🔥 CVE-2026-42208 深度独立研究:源码审计 · 二次发现 · 利用方案

🔥 高危漏洞深度独立研究 · CVSS ≥ 9.8

CVE-2026-42208 深度独立研究:源码审计 · 二次发现 · 利用方案

📊 3 来源🔍 源码审计🧪 PoC
NVD-LatestCISA-KEVPoC-in-GitHub

🔍 源码独立审计

https://github.com/BerriAI/litellm 源码进行独立审计(置信度 62%)。

🧬 根因独立理解

漏洞根因位于 LiteLLM 代理的 API 密钥校验流程中。在 `litellm/proxy/proxy_server.py` 的 `user_api_key_auth` 函数(实际代码路径经由 `litellm/proxy/auth_utils.py` 的 `get_api_key` 调用)中,数据库查询语句使用字符串拼接的方式将调用者提供的 API key 直接混入 SQL 文本,而非通过参数绑定传递。例如,受影响的代码可能形如:`query = f"SELECT token, user_id, team_id FROM LiteLLM_VerificationToken WHERE token = '{bearer_token}'"`,其中 `bearer_token` 来自 `Authorization` 头中 `Bearer` 后的内容,完全受攻击者控制。当请求到达任意 LLM API 路由(如 `/chat/completions`),代理首先从 Header 中提取 key,若 key 未命中缓存,则进入错误处理路径并执行该查询。由于拼接未使用参数化,攻击者在 key 值中注入 SQL 元字符(如单引号、UNION、注释符)即可改变原查询语义,导致数据泄露或篡改。补丁在 1.83.7 中将其改为参数化查询:`query = "SELECT ... WHERE token = ?"`,并将 key 作为独立参数传递,从而阻断 SQL 注入。但修复时需确保所有相关分支都在同一路径中采用参数化,否则其他查询点仍可能残留类似风险。

🛤️ 漏洞触发链路

攻击者向 LiteLLM 代理的任意 LLM API 路由(如 POST /chat/completions)发送 HTTP 请求,在 `Authorization` 头中构造恶意 payload,例如 `Bearer ' UNION SELECT username, password, NULL FROM users--`。代理从 Header 解析出 key 后,先进行缓存查找;若查找失败,会调用数据库校验函数。该校验函数将原始 key 用 f-string 拼接到 SQL 查询中,于是 payload 中的 SQL 代码被数据库解释执行。若注入成功,攻击者可通过响应中的错误信息、行为差异或时间延迟盲注获取数据库内容。由于攻击者未认证即可触发,整个流程从公开接口进入,无需绕过任何前置验证,最终可读取或修改 `LiteLLM_VerificationToken` 等敏感表,获得有效管理员密钥或底层 LLM 凭据。

🔁 二次发现(同类漏洞/扩展攻击面)

  • litellm/proxy/proxy_server.py 中 update_key / generate_key 的日志写入或 upsert 操作: 若在创建或更新 API key 时对输入 key 同样采用 f-string 拼接,则存在同类 SQL 注入,可被已认证低权限用户利用。
  • litellm/proxy/management_endpoints/key_management_endpoints.py 中按 key 查询花费或日志的接口: 若查询条件中的 key 值未参数化,攻击者可注入额外条件,读取其他租户的 spend logs 或整个数据库表。

🩹 修复完整性分析

补丁针对 API key 查询使用了参数化绑定,这是一处正确的修复。但修复是否完整取决于是否存在其他用户可控参数进入 SQL 的代码路径。例如,若 `user_id`、`team_id` 或 `model_id` 等字段在查询条件中同样被字符串拼接,则仍可绕过此单点修复。此外,若参数化后的查询被包含在缓存逻辑或异常处理分支中,攻击者可能通过强制触发另一条未打补丁的代码路径(如 /key/info 接口)继续注入。从描述看 1.83.7 的修复目标明确,但需要系统性审计所有数据库访问层,不能仅依赖单个查询修复。

⚔️ 利用方案设计

利用方案分为三步:第一步,探测注入点。向 `/chat/completions` 发送 `Authorization: Bearer test'`,观察响应是否返回 500 或 SQL 语法错误,确认后端拼接了单引号。第二步,确认注入类型并读取数据。使用 UNION 注入:先通过 `ORDER BY` 确定查询列数,例如 `Bearer x' ORDER BY 1--` 正常、`ORDER BY 5--` 报错则列数为小于 5。然后用 `Bearer x' UNION SELECT token, user_id, expires FROM LiteLLM_VerificationToken--` 直接提取 token 哈希。若数据库为 PostgreSQL,可改用 `UNION SELECT column_name FROM information_schema.columns` 逐列爆破。第三步,利用获取的密钥或绕过认证。若拿到高权限 token,则直接构造合法 `Authorization` 头访问所有管理端点;若仅拿到哈希,可离线破解或使用 `token=null` 的默认逻辑。此外,时间盲注可作为后备:`Bearer x' AND (SELECT pg_sleep(5))--` 延迟响应,逐字符抽取数据。整个攻击无需预先认证,只需构造不同的恶意 Header 即可实现任意数据库读取,进而控制代理管理的所有 LLM 凭据。

🧪 PoC 复现

从 GitHub 公开仓库抓取的实际 PoC 代码(仓库)。

📋 代码元数据语言md来源ridhinva/litellm-sqli-scanner针对性✅ 已验证与漏洞相关(代码含 CVE 引用)依赖见代码注释/README用法详见代码注释中的使用说明

# LiteLLM SQL Injection Scanner

>
CVE-2026-42208 — SQL injection in LiteLLM Proxy

Detects and validates CVE-2026-42208: SQL injection vulnerability in LiteLLM Proxy's database layer.
For authorized security testing only.

## Features

- CVE-2026-42208 SQL injection detection
- LiteLLM Proxy version fingerprinting
- PoC validation payloads
- Structured JSON / text output

## Requirements

- Python 3.8+

## Usage

```bash
python litellm_scanner.py --url http://target:4000      # Scan a target
python litellm_scanner.py --url http://target:4000 --poc # Run PoC validation
python litellm_scanner.py --help                         # Help
```

## Legal

**For authorized testing only.** Only scan systems you own or have explicit written permission to test.

## Author

ridhinva — https://github.com/ridhinva

⚔️ EXP 利用代码

截至分析时,Exploit-DB 未收录该 CVE 的公开利用代码。可利用上述 PoC 进行验证,或关注 Exploit-DB 更新。

🕵️ 检测指纹

针对该 CVE 的自动化检测规则(可直接用于扫描与审计)。

🛡️ Semgrep 审计规则: CVE-2026-42208.yaml

📋 代码元数据语言yaml来源rules/semgrep/CVE-2026-42208.yaml针对性✅ 按 CVE 匹配依赖semgrep用法semgrep --config CVE-2026-42208.yaml

rules:
  - id: CVE-2026-42208-sqli-python
    languages:
      - python
    severity: ERROR
    message: "Potential SQL injection in LiteLLM proxy API key check via string formatting instead of parameterized query"
    patterns:
      - pattern-either:
          - pattern: |f"SELECT * FROM $TABLE WHERE key = $VALUE"
    pattern-not: |f"SELECT * FROM $TABLE WHERE key = ?"
    fix: |
cursor.execute("SELECT * FROM proxy_keys WHERE key = ?",
(key_value,))
    metadata:
      cwe: "CWE-89"
      owasp: "A1: Injection"
      technology: litellm
      references:
        - "https://nvd.nist.gov/vuln/detail/CVE-2026-42208"
  - id: CVE-2026-42208-sqli-python-format
    languages:
      - python
    severity: ERROR
    message: "Potential SQL injection via string formatting in database query"
    patterns:
      - pattern-either:
          - pattern: $QUERY.format(key=$VALUE)
      - pattern-not: $QUERY.format(key="?")
    fix: |
cursor.execute("SELECT * FROM proxy_keys WHERE key = ?",(key_value,))
    metadata:
      cwe: "CWE-89"
      owasp: "A1: Injection"
      technology: litellm
      references:
        - "https://nvd.nist.gov/vuln/detail/CVE-2026-42208"

🛡️ CodeQL 审计规则: CVE-2026-42208.ql

📋 代码元数据语言ql来源rules/codeql/CVE-2026-42208.ql针对性✅ 按 CVE 匹配依赖codeql用法codeql database run

/**
 * @kind path-problem
 * @id python/sql-injection/cve-2026-42208
 * @name SQL injection in LiteLLM proxy database query
 * @description User-controlled Authorization header value is concatenated into a database query instead of being passed as a parameter,
allowing SQL injection.
 * @problem.severity error
 * @tags security
 *       external/cwe/cwe-089
 */
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.ApiGraphs

class SqlInjectionConfig extends TaintTracking::Configuration {SqlInjectionConfig() {this = "SqlInjectionConfig" }
override predicate isSource(DataFlow::Node source) {exists(DataFlow::Node n |n.asExpr() = any(Subscript s |s.getObject().(Name).getId() = "headers").getItem(_) or
      source.asExpr() = any(Call c |c.getFunc().(Attribute).getAttrName() = "get" and
        c.getFunc().(Attribute).getObject().(Name).getId() = "headers"
      )
    )
  }override predicate isSink(DataFlow::Node sink) {
exists(DataFlow::Node n |n.asExpr() = any(Call c |c.getFunc().(Attribute).getAttrName() = "execute" or
        c.getFunc().(Attribute).getAttrName() = "executemany" or
        c.getFunc().(Attribute).getAttrName() = "executescript"
      ) and
      sink = n
    )
  }override predicate isAdditionalTaintStep(DataFlow::Node node1,DataFlow::Node node2) {
any(FString fstr).getAFormattedValue() = node1.asExpr() and
    node2.asExpr() = fstr
  }}from DataFlow::PathNode source,DataFlow::PathNode sink,SqlInjectionConfig config
where config.hasFlowPath(source,sink)
select sink.getNode(),source,sink,"User-controlled input from Authorization header reaches database execute() call,allowing SQL injection."

🤖 高危漏洞深度独立研究引擎生成 · 2026-08-13 03:01

[!] CONTACT_CHANNELS

如需商务合作、技术咨询或漏洞反馈,请通过以下离岸节点联系作者。

> PING_AUTHOR (@A1RedTeam)