Hack The Box: TheNotebook

Jump Ahead: EnumUserRootResources


To solve this machines, we begin by enumerating open ports – finding ports 22 and 80 open. On the webserver, we register an account to gain access to the notes feature. Once we have an account created, we are issued a JWS. Due to a vulnerability within the JWS, we are able to forge an administrative token – gaining access to the admin section of the site. With admin access, we are able to obtain a reverse shell – as the www-data user. As www-data, we are able to read a backup archive, and gain the SSH private key for the noah user – allowing us to read user.txt. As noah, we are able to execute a docker container as root without a password. Lastly, we are able to exploit a component of docker to execute a reverse shell as root – gaining access to root.txt.


Like all machines, we begin by enumerating open ports using nmap. From our scan, we find ports 22 and 80 open.

$ sudo nmap -Pn -p- -sV -A -oA enum/nmap/tcp-all-scripts
# Nmap 7.91 scan initiated Sun Mar  7 14:32:58 2021 as: nmap -Pn -p- -sV -A -oA enum/nmap/tcp-all-scripts
Nmap scan report for
Host is up (0.045s latency).

22/tcp    open     ssh     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)
|   256 e7:81:d6:6c:df:ce:b7:30:03:91:5c:b5:13:42:06:44 (ECDSA)
|_  256 c6:06:34:c7:fc:00:c4:62:06:c2:36:0e:ee:5e:bf:6b (ED25519)
80/tcp    open     http    nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: The Notebook - Your Note Keeper
10010/tcp filtered rxapi
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 5.0 - 5.3 (95%), Linux 4.15 - 5.6 (95%), Linux 3.1 (95%), Linux 3.2 (95%), Linux 5.3 - 5.4 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 2.6.32 (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 3.2 - 4.9 (92%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 22/tcp)
1   44.81 ms
2   44.97 ms

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Mar  7 14:33:23 2021 -- 1 IP address (1 host up) scanned in 24.58 seconds

Next, we use gobuster to bruteforce files and directories. From this, we learn of a couple interesting endpoints.

# Interesting endpoints found via gobuster
/admin (Status: 403)
/logout (Status: 302)
/login (Status: 200)
/register (Status: 200)

Getting User

Since the webserver is the only avenue we see forward, we head over to the website. We are presented with a page that tells us it’s a note taking app, and to get started, we should register an account.

After logging in, we are issued 2 cookies, and redirected to our home page. One of the cookies is a UUID, and the other appears to be a JWT. After decoding the JWT, we learn it may potentially fetch its key file from a remote location – noted by http://localhost:7070/privKey.key in the screenshot.

Going to the notes page, we are allowed to add notes, and attempt to test for injection vulnerabilities. Our checks fail, so we decide to focus on the JWT.

To test the key fetch for a vulnerability, we first decode the JWT, and edit the server and port of the “kid” field to point to our machine on port 80. Next, we re-encode it. Once re-encoded, we replace the JWT in our browser’s cookie jar (via devtools). Now to test the key fetch, we start a webserver on our machine, and refresh the page. After doing so, we do get a request for /privKey.key from the webserver.

Since we are able to control the private key of the JWT, we should be able to forge a JWT for any user we want. Since we saw the /admin directory, we can assume admin is a valid user, and will have access to that directory. To forge the JWT, we copy it from our request history or browser cookie jar, and head over to https://jwt.io. Once there, we change the algorithm to “RS256” and the site will generate a sample JWT with public and private keys. Next, we post our JWT into the “Encoded” field, which decodes the JWT for us (on the right), and allows us to make edits. For our edits, we set “username” to “admin”, and change “admin_cap” to “1”. Next, we set the “kid” server and port values to point to our machine. Lastly, we copy the private key to our local machine and name it “privkey.key”.

Before we attempt to access the /admin directory, we replace the JWT in our cookie jar with the one we created on https://jwt.io. So the remote machine can access our key, we use python to serve a HTTP server, so the remote machine may be able to access the private key. Then in our browser, we attempt to access the /admin directory. In our terminal, we see a connection to our HTTP server, and in the browser, we see the admin interface.

Since admin has a “Notes” link as well, we think it is a good idea to check it out for possible sensitive information. There are a few notes, notably, we read that php files are able to be executed, and another note that backups are scheduled.

Since there is also an “Upload” link, we head over to this to see if we are able to upload a php reverse shell. We are presented with an upload form, and supply a php reverse shell script. *For this, check out laudanum* Once the script uploads, it is listed under our files – with the name replaced by a hash. Assuming the “View” option will execute the reverse shell, we start our socket listener with netcat. Once we click “View”, we do indeed get a reverse shell as www-data.

Now that we have access to the machine, we decide to look into backups, since one of admin‘s notes mentioned them. Going to /var/backups/, we see that home.tar.gz is globally readable.

By the name, we assume this is a backup of /home, so we copy it to /tmp and extract it. After we extract the archive, we are given a backup of noah‘s SSH key among other files.

We copy the SSH private key to our local machine, set the correct permissions, and log into the machine as noah. We now have access to read user.txt

Getting Root

Now that we have a shell as noah, we begin our initial enumeration. The first thing we check is if we can run any commands as another user using sudo -l. From this, we learn we are able to run sudo /usr/bin/docker exec -it webapp-dev01* as root. To verify this, we try to run bash on the webapp-dev01 container. Doing so, we do indeed get a shell as root.

Since the sudo configuration is pretty much hard coded that we can only run a command on the container, our usual container escapes are not likely to work here. Before we attempt to dive deeper into privilege escalation via docker, we run linpeas to automate the rest of our enumeration. Doing so, we see a note that runc is installed and that we may be able to use it to escalate our privileges.

The rest of our linpeas output did not point out anything else immediately interesting, so we decide to explore runc more. First, we check its version, then we check the version for vulnerabilities via searchsploit. From searchsploit, we learn of 2 possible container breakouts, but docker has to be less than version “18.09.2”. We check docker‘s version, and see that it is version 18.06.0, which means these exploits should work.

Using this exploit as our guide, we download the archive to our local machine, extract the files, make the instructed changes, and upload the files to the /tmp directory of the container. Next, we start our reverse shell listener on our local machine. Lastly, to execute the reverse shell, we run pwn.sh as instructed, then run sudo /usr/bin/docker exec -it webapp-dev01 /bin/sh from the container’s host (as noah). Once we execute the docker shell, we get a reverse shell on our local machine as root. We now are able to read root.txt.

Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!