
Proof-of-Concept-Exploit für CVE-2026-25895 FUXA Nicht authentifizierter Path Traversal -> Arbitrary File Write -> RCE
Nicht authentifiziertes, Pre-Auth beliebiges Dateischreiben gegen FUXA, eine Node.js-basierte SCADA/HMI-Plattform. Verkettet zu Remote Code Execution über mehrere verschiedene Post-Write-Primitiven. Funktioniert selbst wenn secureEnabled = true (Authentifizierung aktiviert) ist, da der angreifbare Endpunkt keine Middleware hat.
Auch verfügbar auf ExploitDB: https://www.exploit-db.com/exploits/52568
| Feld | Wert |
|---|---|
| CVE-ID | CVE-2026-25895 |
| Betroffen | FUXA <= 1.2.9 |
| Behoben | FUXA 1.2.10 |
| Anbieter | frangoteam / FUXA |
| Angriffsvektor | Netzwerk (HTTP/HTTPS) |
| Authentifizierung | Keine erforderlich |
| Auswirkung | Beliebiges Dateischreiben, Remote Code Execution |
| CVSS v3.1 | 9.8 (Kritisch) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Forscher | Anthony Cihan (Hann1bl3L3ct3r) |
Der Endpunkt POST /api/upload von FUXA (server/api/projects/index.js:193) ist ohne Middleware registriert und umgeht sowohl die secureFnc-JWT-/API-Key-Prüfung als auch die Admin-Berechtigungsstufe, die auf alle anderen Projektverwaltungs-Endpunkte angewendet wird. Innerhalb des Handlers wird das JSON-Body-Feld destination mit nur einem führenden Unterstrich und ohne Normalisierung oder Containment-Prüfung in einen Dateisystempfad eingefügt:
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
filePath = path.join(destinationDir, fullPath || fileName);
fs.writeFileSync(filePath, basedata, encoding);
Ein destination-Wert der Form a/../../../../../etc (wobei a das führende Unterstrich-Präfix absorbiert) lässt path.resolve von Node aus appDir an jeden Ort ausbrechen, den der FUXA-Prozess beschreiben kann. Da fs.writeFileSync von einem bedingten fs.mkdirSync(dir, { recursive: true }) vorausgeht, kann der Angreifer auch bei Bedarf übergeordnete Verzeichnisse erstellen.
Das Ergebnis ist eine nicht authentifizierte, beliebige Dateischreib-Primitive, die über den Standard-HTTP-Port (1881) erreichbar ist und unabhängig davon, ob der FUXA-Administrator das Login aktiviert hat, pre-auth ausgenutzt werden kann.
Ein nicht authentifizierter entfernter Angreifer kann:
settings.js ersetzen, um beim nächsten Neustart von FUXA Codeausführung zu erreichen/etc/cron.d/<file>) ablegen, um innerhalb von 60 Sekunden Codeausführung zu erreichen, wenn FUXA als root läuft (Standard im Docker-Image des Anbieters)/root/.ssh/authorized_keys oder in ~/.ssh/authorized_keys eines beliebigen Benutzers ablegenDies ist ein pre-auth kritischer Befund auf einer ICS/SCADA-Plattform, die zur Steuerung industrieller Prozesse verwendet wird.
| Version | Status |
|---|---|
<= 1.2.9 | Anfällig |
1.2.10+ | Behoben |
Bestätigt ausnutzbar gegen eine saubere Installation von FUXA 1.2.9 auf Ubuntu Server.
Einzeldatei-Python-3-Skript, eine Drittanbieter-Abhängigkeit (requests).
pip install requests
python3 fuxapwn.py --help
Identifizieren des laufenden OS-Benutzers, der Node-RED-Exposition und anderer Konten auf dem Host, ohne etwas Ungewöhnliches zu schreiben:
python3 fuxapwn.py -u http://target:1881 --mode recon \
--probe-root --probe-home
Schreiben einer neutralen /tmp/healthcheck-Markierung (keine CVE-spezifischen IOCs im Dateinamen oder -inhalt):
python3 fuxapwn.py -u http://target:1881 --mode canary
Wenn FUXA als root läuft, einen Cron-Datei ablegen, die innerhalb von 60 Sekunden ausgelöst wird, ohne dass ein Neustart von FUXA erforderlich ist:
python3 fuxapwn.py -u http://target:1881 --mode cron \
--cron-cmd 'id > /tmp/fx.txt 2>&1'
settings.js-ErsatzInstallieren eines HTTP-Webshell-Listeners innerhalb des FUXA-Node-Prozesses (aktiviert sich beim nächsten Kaltstart, da require() Module cached), dann in ein interaktives REPL wechseln, sobald FUXA neu startet:
# 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
Vollständige modusbezogene Flags-Referenz: python3 fuxapwn.py --help.
GET /api/settings (server/api/index.js:103) ist ohne Middleware registriert und gibt die Live-Laufzeitkonfiguration zurück, leicht redigiert (der Server löscht secretCode und smtp.password vor dem Senden). Die verbleibenden Felder lecken absolute Pfade (appDir, workDir, userSettingsFile, logsDir, uploadFileDir), die typischerweise den Dienstbenutzer identifizieren, sowie nodeRedEnabled, was ein direkter Hinweis auf einen zweiten nicht authentifizierten RCE-Pfad ist (siehe unten).
Wenn FUXA via npm start unter einem lokalen Benutzerkonto gestartet wird und die Installationspfade den Benutzer nicht codieren (z. B. liegt die Installation unter /opt, /tmp oder einem generischen /app), und /root/ nicht beschreibbar ist, fällt der PoC auf das Iterieren von /home/<kandidat>/ mit Null-Byte-Schreibvorgängen zurück.
Der FUXA-Upload-Handler ruft bedingt fs.mkdirSync(parent, { recursive: true }) vor fs.writeFileSync auf, was eine EACCES-Mehrdeutigkeit erzeugt: Ein nicht existierendes /home/<user>/ scheitert mit EACCES beim mkdir-Versuch (der Prozess kann keine Verzeichnisse unter dem root-eigenen /home/ erstellen), während ein existierendes /home/<other>/ mit Modus 0700 mit EACCES beim Schreiben selbst scheitert. Gleicher errno, andere Bedeutung. Der PoC löst dies auf, indem er das Syscall-Token aus der vom Server weitergeleiteten err.message (libuv-Format "<CODE>: <reason>, <syscall> '<path>'") parst und nur EACCES-beim-Öffnen (oder jeden non-mkdir-Syscall) als "anderer Benutzer existiert" meldet. Dies eliminiert die falsch-positive Lateral-Movement-Liste, die eine naive reine-errno-Sonde produzieren würde.
Wenn nodeRedEnabled = true in den durchgesickerten Einstellungen ist, sind die eingebetteten Node-RED-Admin-Endpunkte von FUXA (/nodered/flows/deploy, etc.) nicht authentifiziert erreichbar, da eine Referer-Header-Whitelist-Prüfung (node-red/index.js:134-136) jede Anfrage akzeptiert, deren Referer /editor, /viewer oder /lab enthält. Dies liefert eine sofortige nicht authentifizierte RCE über einen Function-Node, ohne dass ein Neustart erforderlich ist. Der recon-Modus kennzeichnet diese Bedingung; Betreiber sollten dies bevorzugen, wenn verfügbar.
settings.js-Payloads bewahren ZielkonfigurationBeim Generieren eines settings.js-Ersatzes (Modi settings-rce und webshell) ruft der PoC zuerst die Live-Konfiguration via /api/settings ab und serialisiert sie als JSON als module.exports-Body (JSON ist eine gültige JavaScript-Objektliteral-Teilmenge). Dies bewahrt die uiPort, allowedOrigins, secureEnabled und andere Laufzeiteinstellungen des Ziels, sodass die Anwendung nach dem Ersatz normal weiterläuft. Der PoC warnt explizit, wenn das Ziel secureEnabled = true oder einen smtp-Block hat, da die serverseitige Redigierung secretCode (JWT-Fallback wird aktiviert) und smtp.password (E-Mail wird bis zur manuellen Wiederherstellung unterbrochen) entfernt.
POST /api/upload von nicht authentifizierten Quellen, insbesondere mit destination-Werten, die .. oder absolute Dateisystempfade in der Antwort enthaltenGET /api/settings von nicht authentifizierten Quellen (normalerweise nur von der authentifizierten UI verwendet)/tmp/healthcheck*, /tmp/.fuxa-probe-* oder /home/*/.fuxa-probe-* übereinstimmen (Standard-Canary- und Probe-Marker-Dateinamen — der PoC erlaubt das Überschreiben dieser, um offensichtliche IOCs zu vermeiden, daher schließt Abwesenheit eine Ausnutzung nicht aus)settings.js, /etc/cron.d/* oder ~/.ssh/authorized_keys, die keiner Administratoraktion entsprechen/api/upload ist in der behobenen Version durch die Standard-Middleware-Kette geschützt./api/upload und /api/settings blockiert./root/-Schreiben, kein /etc/cron.d/-Schreiben, keine hostweite Persistenz über Cron).nodeRedEnabled = false), wenn es nicht aktiv genutzt wird.1.2.10server/api/projects/index.js:193 in FUXA 1.2.9Forschung, PoC und Dokumentation von Anthony Cihan (Hann1bl3L3ct3r), Leiter der Offensiven Sicherheit.
Dieses Repository enthält funktionalen Exploit-Code für eine kritische Schwachstelle in einem ICS/SCADA-Produkt. Es wird im Rahmen verantwortungsvoller Offenlegungsprinzipien nach dem Patch durch den Anbieter veröffentlicht, zum Nutzen von Verteidigern (Erkennungsautoren, Incident-Responder) und autorisierten Sicherheitstestern.
Verwenden Sie diesen Code nur gegen Systeme, die Sie besitzen, oder für die Sie eine ausdrückliche, schriftliche Genehmigung zum Testen haben. Die unbefugte Nutzung dieses Codes gegen Systeme Dritter ist in den meisten Rechtsordnungen illegal und wird vom Autor als solche behandelt. Der Autor übernimmt keine Haftung für Missbrauch.
Wenn Sie ein FUXA-Betreiber sind und Hilfe bei der Validierung Ihres Patch-Levels gegen diesen PoC unter kontrollierten Bedingungen wünschen, kontaktieren Sie den Autor.
Veröffentlicht für autorisierte Sicherheitstest- und defensive Forschungszwecke. Vollständige Bedingungen siehe LICENSE.
| Modus | Zweck |
|---|
recon | Nicht authentifiziertes Informationsleck via GET /api/settings; leitet den laufenden OS-Benutzer aus absoluten Pfaden ab; meldet Node-RED-Status; optionale --probe-root- und --probe-home-aktive Sonden |
canary | Beweis der Dateischreibprimitive mit einem neutralen Standardpfad |
settings-rce | Ersetzt settings.js mit einem Payload, der beim nächsten Kaltstart von FUXA einen konfigurierbaren Befehl ausführt |
ssh-key | Schreibt einen öffentlichen Schlüssel in authorized_keys eines Zielbenutzers |
drop | Beliebiger Dateiabwurf an einen beliebigen absoluten Pfad |
cron | Legt /etc/cron.d/<name> für RCE innerhalb von 60 Sekunden ab, ohne auf einen Neustart von FUXA zu warten (erfordert FUXA als root) |
webshell | Installiert einen HTTP-Webshell-Listener innerhalb des FUXA-Node-Prozesses via settings.js-Ersatz (aktiviert beim nächsten Kaltstart) |
webshell-exec | Client für eine bereits installierte Webshell; einzelner Befehl oder --interact REPL |