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