
Exploit POC para CVE-2026-25895 FUXA: Path Traversal sin autenticación -> Escritura arbitraria de archivos -> RCE
Escritura arbitraria de archivos no autenticada (pre-auth) contra FUXA, una plataforma SCADA/HMI basada en Node.js. Encadena a ejecución remota de código mediante varias primitivas posteriores a la escritura. Funciona incluso cuando secureEnabled = true (autenticación activada) porque el endpoint vulnerable no tiene middleware asociado.
También disponible en ExploitDB: https://www.exploit-db.com/exploits/52568
| Campo | Valor |
|---|---|
| ID CVE | CVE-2026-25895 |
| Afectado | FUXA <= 1.2.9 |
| Parcheado | FUXA 1.2.10 |
| Proveedor | frangoteam / FUXA |
| Vector de ataque | Red (HTTP/HTTPS) |
| Autenticación | No se requiere |
| Impacto | Escritura arbitraria de archivos, ejecución remota de código |
| CVSS v3.1 | 9.8 (Crítico) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Investigador | Anthony Cihan (Hann1bl3L3ct3r) |
El endpoint POST /api/upload de FUXA (server/api/projects/index.js:193) está registrado sin middleware, omitiendo tanto la comprobación secureFnc de JWT / clave API como la compuerta de permisos de administrador aplicada a todos los demás endpoints de gestión de proyectos. Dentro del manejador, el campo destination del cuerpo JSON se concatena a una ruta del sistema de archivos con solo un guion bajo inicial y sin normalización ni comprobación de contención:
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
filePath = path.join(destinationDir, fullPath || fileName);
fs.writeFileSync(filePath, basedata, encoding);
Un valor de destination con la forma a/../../../../../etc (donde a absorbe el prefijo de guion bajo inicial) hace que path.resolve de Node salga de appDir hacia cualquier ubicación en la que el proceso de FUXA pueda escribir. Debido a que fs.writeFileSync está precedido por un fs.mkdirSync(dir, { recursive: true }) condicional, el atacante también puede crear los directorios padre según sea necesario.
El resultado es una primitiva de escritura arbitraria de archivos no autenticada, accesible en el puerto HTTP por defecto (1881), explotable pre-auth independientemente de si el administrador de FUXA ha habilitado el inicio de sesión.
Un atacante remoto no autenticado puede:
settings.js para lograr ejecución de código en el siguiente reinicio de FUXA/etc/cron.d/<file>) para ejecución de código en menos de 60 segundos cuando FUXA se ejecuta como root (el valor por defecto en la imagen Docker del proveedor)/root/.ssh/authorized_keys o en el ~/.ssh/authorized_keys de cualquier usuarioEste es un hallazgo de severidad crítica pre-auth en una plataforma ICS/SCADA utilizada para operar procesos industriales.
| Versión | Estado |
|---|---|
<= 1.2.9 | Vulnerable |
1.2.10+ | Parcheado |
Explotación confirmada contra una instalación limpia de FUXA 1.2.9 en Ubuntu Server.
Script Python 3 de un solo archivo, con una única dependencia de terceros (requests).
pip install requests
python3 fuxapwn.py --help
Identifica el usuario del SO en ejecución, la exposición de Node-RED y cualquier otra cuenta en el host sin escribir nada inusual:
python3 fuxapwn.py -u http://target:1881 --mode recon \
--probe-root --probe-home
Escribe un marcador neutral /tmp/healthcheck (sin IOCs específicos del CVE en el nombre del archivo ni en el contenido):
python3 fuxapwn.py -u http://target:1881 --mode canary
Si FUXA se ejecuta como root, coloca un archivo cron que se active en menos de 60 segundos sin necesidad de reiniciar FUXA:
python3 fuxapwn.py -u http://target:1881 --mode cron \
--cron-cmd 'id > /tmp/fx.txt 2>&1'
settings.jsInstala un listener de webshell HTTP dentro del proceso Node de FUXA (se activa en el siguiente arranque en frío, ya que require() almacena en caché los módulos) y luego inicia un REPL interactivo cuando FUXA se reinicie:
# Stage the payload — replaces settings.js but preserves the target's
# real configuration (uiPort, allowedOrigins, secureEnabled, etc.) so the
# application keeps serving normally.
python3 fuxapwn.py -u http://target:1881 --mode webshell \
--appdata /opt/FUXA/server/_appdata --ws-port 31337
# Once FUXA restarts, connect to the installed webshell.
python3 fuxapwn.py -u http://target:1881 --mode webshell-exec \
--ws-host target --ws-port 31337 \
--ws-path /_abc123 --ws-token <printed-above> --interact
Referencia completa de flags por modo: python3 fuxapwn.py --help.
GET /api/settings (server/api/index.js:103) está registrado sin middleware y devuelve la configuración de runtime en vivo, ligeramente depurada (el servidor elimina secretCode y smtp.password antes de enviarla). Los campos restantes filtran rutas absolutas (appDir, workDir, userSettingsFile, logsDir, uploadFileDir) que normalmente identifican al usuario del servicio, además de nodeRedEnabled, que es un indicador directo de una vía secundaria de RCE no autenticada (ver más abajo).
Cuando FUXA se lanza mediante npm start bajo una cuenta de usuario local y las rutas de instalación no incorporan el nombre del usuario (p. ej., la instalación se encuentra en /opt, /tmp o un /app genérico), y /root/ no es escribible, el POC recurre a iterar /home/<candidate>/ con escrituras de cero bytes.
El manejador de subida de FUXA llama condicionalmente a fs.mkdirSync(parent, { recursive: true }) antes de fs.writeFileSync, lo que crea una ambigüedad de EACCES: un /home/<user>/ inexistente falla con EACCES en el intento de mkdir (el proceso no puede crear directorios bajo /home/ propiedad de root), mientras que un /home/<other>/ existente con modo 0700 falla con EACCES en la propia escritura. Mismo errno, significado diferente. El POC desambigua analizando el token de syscall del err.message reenviado por el servidor (formato libuv "<CODE>: <reason>, <syscall> '<path>'") y solo reporta un EACCES en open (o cualquier syscall que no sea ) como "existe otro usuario". Esto elimina la lista de movimiento lateral con falsos positivos que produciría una sonda ingenua basada únicamente en errno.
Si nodeRedEnabled = true en la configuración filtrada, los endpoints de administración de Node-RED integrado en FUXA (/nodered/flows/deploy, etc.) son accesibles sin autenticación debido a una comprobación de lista blanca del encabezado Referer (node-red/index.js:134-136) que acepta cualquier solicitud cuyo Referer contenga /editor, /viewer o /lab. Esto produce una RCE no autenticada instantánea a través de un nodo de función sin necesidad de reiniciar. El modo recon señala esta condición; los operadores deberían preferirla cuando esté disponible.
settings.js preservan la configuración del objetivoAl generar un reemplazo de settings.js (modos settings-rce y webshell), el POC primero obtiene la configuración en vivo mediante /api/settings y la serializa en JSON como cuerpo de module.exports (JSON es un subconjunto válido de literal de objeto JavaScript). Esto preserva el uiPort, allowedOrigins, secureEnabled y otros ajustes de runtime del objetivo para que la aplicación continúe sirviendo con normalidad tras el reemplazo. El POC advierte explícitamente cuando el objetivo tiene secureEnabled = true o un bloque smtp, ya que la depuración en el lado del servidor elimina secretCode (el fallback de JWT se activará) y smtp.password (el correo dejará de funcionar hasta que se restaure manualmente).
POST /api/upload desde fuentes no autenticadas, especialmente con valores de destination que contengan .. o rutas absolutas del sistema de archivos en la respuestaGET /api/settings desde fuentes no autenticadas (normalmente solo lo utiliza la interfaz autenticada)/tmp/healthcheck*, /tmp/.fuxa-probe-* o /home/*/.fuxa-probe-* (nombres de archivo marcadores por defecto de canary y sondas — el POC permite anularlos para evitar IOCs evidentes, por lo que su ausencia no descarta la explotación)settings.js, /etc/cron.d/* o ~/.ssh/authorized_keys que no correspondan a una acción de un administrador/api/upload está protegido por la cadena de middleware estándar en la versión parcheada./api/upload y /api/settings./root/, sin escritura en /etc/cron.d/, sin persistencia en todo el host mediante cron).nodeRedEnabled = false) si no se utiliza activamente.1.2.10 de FUXAserver/api/projects/index.js:193 en FUXA 1.2.9Investigación, PoC y redacción por Anthony Cihan (Hann1bl3L3ct3r), Líder de Seguridad Ofensiva.
Este repositorio contiene código de exploit funcional para una vulnerabilidad crítica en un producto ICS/SCADA. Se publica bajo los principios de divulgación responsable, después del parcheo por parte del proveedor, en beneficio de los defensores (autores de detecciones, respondedores de incidentes) y de los testers de seguridad autorizados.
Úsalo únicamente contra sistemas que te pertenezcan o para los que tengas autorización explícita y por escrito para realizar pruebas. El uso no autorizado de este código contra sistemas de terceros es ilegal en la mayoría de las jurisdicciones y el autor lo tratará como tal. El autor no acepta ninguna responsabilidad por el uso indebido.
Si eres operador de FUXA y quieres ayuda para validar tu nivel de parcheo frente a este PoC en condiciones controladas, contacta con el autor.
Publicado con fines de pruebas de seguridad autorizadas e investigación defensiva. Consulta LICENSE para conocer los términos completos.
| Modo | Propósito |
|---|
recon | Fuga de información no autenticada mediante GET /api/settings; deduce el usuario del SO en ejecución a partir de rutas absolutas; informa del estado de Node-RED; sondas activas opcionales --probe-root y --probe-home |
canary | Prueba de la primitiva de escritura de archivos con una ruta neutral por defecto |
settings-rce | Reemplaza settings.js con un payload que ejecuta un comando configurable en el siguiente arranque en frío de FUXA |
ssh-key | Escribe una clave pública en el authorized_keys de un usuario objetivo |
drop | Colocación arbitraria de archivos en cualquier ruta absoluta |
cron | Coloca /etc/cron.d/<name> para lograr RCE en menos de 60 segundos sin esperar un reinicio de FUXA (requiere que FUXA se ejecute como root) |
webshell | Instala un listener de webshell HTTP dentro del proceso Node de FUXA mediante el reemplazo de settings.js (se activa en el siguiente arranque en frío) |
webshell-exec | Cliente para una webshell ya instalada; comando único o REPL con --interact |
mkdir