> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# LLM 网关被攻破：LiteLLM 三个漏洞如何偷走 provider key
- URL: https://www.hubwiz.com/blog/litellm-gateway-api-key-theft/
- Published: 2026-10-23T17:46:27.000Z
- Updated: 2026-10-23T17:57:03.000Z
- Description: 一个只做认证、不做角色检查的配置端点，让低权限用户拿到管理员、读出 /etc/passwd，再把 provider key 送到攻击者服务器。
- Author: admin
- Tags: AI 安全, LLM 网关, LiteLLM, 漏洞分析, API Key

**一句话结论**：LLM 网关把“所有模型的 key”收拢到一处，省掉了每个应用各自管凭据的麻烦，也把风险集中到了一个进程上。Check Point 在 LiteLLM 1.77.7 上找到的两组漏洞，刚好演示了这条路径有多短：**一个只做认证、不做角色检查的配置端点**，让任意登录用户改掉运行时配置，进而读任意文件、接管管理员、执行远程代码；另一条链则利用“客户端自带凭据”功能，把网关里配置的 provider key **直接转发到攻击者的服务器**。

**披露**：本文编译自 Check Point Research《When LLM Gateways Go Wrong: Findings from LiteLLM》（**2026-10-13**）。研究针对 LiteLLM **v1.77.7**，漏洞已在 **v1.83.9** 修复；CVE 与 GHSA 编号以原文为准。**本文未独立复现**，代码片段为原文节选与整理，PoC 中的域名、密钥、路径均为演示值。

---

## 一、为什么 LLM 网关是整套系统的钥匙串

LLM 网关的位置很特殊：它夹在应用和一家或多家模型供应商之间。应用不再直接调模型 API，而是把请求交给网关，由网关转发到对应的后端。

在这个位置上，网关通常集中做这些事：

- 供应商抽象（一套接口对接多家模型）
- **凭据管理**（provider key 只存在网关里）
- 请求路由、限流、用量统计、日志
- 失败回退、多供应商负载均衡
- 按配置选择模型

这带来的好处是应用侧不用碰 key；代价是**网关进程成了所有 key 的集合点**。它被攻破，或者它的管理面被绕过，损失不是“一个应用的一条 key”，而是“整套系统在所有供应商那里的身份”。

### 1.1 先看三条链

```
低权限用户
  └─ POST /config/update（只认证，不查角色）
       ├─ 改 UI_LOGO_PATH          → 任意文件读
       ├─ 改 UI_USERNAME / UI_PASSWORD → 管理员接管
       └─ 注册 pass-through 端点    → 从 S3 加载并执行代码

任意已认证用户
  └─ POST /chat/completions { user_config: { api_base: 攻击者 } }
       └─ 网关带着默认 header 里的 provider key 发请求 → key 泄露

```

两条链的共同点：**都把“调用者可控的输入”变成了“服务端自己的行为”**。

## 二、漏洞一：/config/update 只查“你是谁”，不查“你能不能”

LiteLLM 的代理暴露了一个管理端点 `POST /config/update`，用于在运行时修改配置：环境变量、模型列表、代理的通用设置。问题出在它少了一个检查。

### 2.1 缺的是角色检查

在 LiteLLM 的其他管理端点里，代码会显式验证调用者是不是 `PROXY_ADMIN`。原文拿 `/invitation/new` 做了对比：

```python
# 安全的写法：先认证，再查角色
if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN:
    raise HTTPException(status_code=400, detail="not allowed")

```

而 `/config/update` 只挂了通用的 `user_api_key_auth`，**没有再检查角色**：

```python
@router.post(
    "/config/update",
    dependencies=[Depends(user_api_key_auth)],   # 只做认证
    include_in_schema=False,
)
async def update_config(config_info: ConfigYAML):
    # 缺少：if user_role != PROXY_ADMIN: raise ...
    await proxy_config.save_config(new_config=config)

```

### 2.2 UI token 的分类只看一个字段

`user_api_key_auth` 会把授权委托给 `common_checks`。这个函数先判断 token 是“UI 会话”还是“API key”，判断依据只有一个：`team_id` 是不是 `litellm-dashboard`。

而任何用户登录 LiteLLM 的 Web UI 时，后端都会签发一个临时的 API key，并把它挂到 `litellm-dashboard` 这个特殊 team 上：

```python
"team_id": "litellm-dashboard",   # UI 登录签发的 key 都带这个

```

于是：**只要你能登录 UI，你的 token 就会被归类为 `ui`。**

### 2.3 前缀匹配把 /config/update 放了进来

被归类为 `ui` 之后，授权走的是 `_is_ui_route()`，而不是更严格的 API 路由检查。`ui_routes` 列表里有一项 `/config`——原本大概只是想允许 UI 读配置。

但 `_is_ui_route()` 用的是 `startswith`：

```python
any(route.startswith(allowed_route) for allowed_route in allowed_routes)

```

`/config/update` 正好以 `/config` 开头，于是**通过了检查**。

前缀匹配是这类漏洞的常客：授权表写的是“允许 `/config`”，实现用的是 `startswith`，于是所有以 `/config` 开头的路径都被放行。修法不是把 `/config` 改成 `/config/`——那只是把洞挪到 `/config/xxx`——而是**按精确路由或“方法 + 路径”匹配**，并给管理端点单独维护一张表。

### 2.4 结果：任何登录用户都能改运行时配置

这三步叠起来，任何已认证用户——包括只有 `Internal User` 基线角色的账号——都能调用 `POST /config/update`。原文对影响的描述是：**任意文件读取、管理员账号接管、通过 pass-through 端点执行远程代码**。

这不是一个“需要管理员误操作”的漏洞，而是一个**低权限用户就能触发**的权限提升。

## 三、从“改配置”到读文件、当管理员、跑代码

有了“能改环境变量和通用设置”这个原语，后面三条链都很直接。

### 3.1 任意文件读：`UI_LOGO_PATH`

`/config/update` 接受 `environment_variables`，会把键值解密后写进 `os.environ`。其中一个变量 `UI_LOGO_PATH` 控制 `/get_image` 端点读哪个文件。

```json
POST /config/update
{
  "environment_variables": {
    "UI_LOGO_PATH": "/etc/passwd"
  }
}

```

然后请求 `GET /get_image`，服务端直接 `FileResponse(logo_path)`，把 `/etc/passwd` 当图片返回。

**能读 `/etc/passwd`，就能读配置文件、部署凭据、其他服务的 token。** 原文的 PoC 返回了完整的 passwd 内容，`content-type` 还标着 `image/jpeg`。

### 3.2 管理员接管：`UI_USERNAME` / `UI_PASSWORD`

同一套环境变量注入，还能改掉 UI 的登录凭据。`/login` 直接从环境变量读用户名和密码，匹配成功就授予 `PROXY_ADMIN`：

```python
ui_username = os.getenv("UI_USERNAME", "admin")
ui_password = os.getenv("UI_PASSWORD", None)
...
user_role = LitellmUserRoles.PROXY_ADMIN

```

于是攻击者先注入自己的用户名密码，再用它登录，就成了代理管理员。原文的截图显示，登录后拿到的是内置 `admin` 账号的 JWT。

### 3.3 RCE：`pass_through_endpoints` \+ `s3://`

LiteLLM 支持 pass-through 端点，可以把请求代理到外部服务，也可以加载自定义 adapter。`target` 支持 `s3://` 和 `gcs://`——以这两个前缀开头时，LiteLLM 会把远程 Python 文件下载下来，用 `importlib` 执行：

```python
spec = importlib.util.spec_from_file_location(module_path, local_file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)   # 顶层代码在此执行

```

攻击者只需要在 S3 放一个 Python 文件，顶层写一句 `os.makedirs("/tmp/litellm-pwned")`，再通过 `/config/update` 注册成 pass-through 端点。请求一处理，目录就被创建——**代码执行确认**。换成反弹 shell 或窃取凭据，只是载荷不同。

### 3.4 三条链的共同点

读文件、接管管理员、RCE 看起来是三个洞，其实共用一个原语：**能写环境变量和通用配置**。一旦攻击者能改配置，他就能改认证、改文件路径、改模块加载地址——这三件事都不需要再找一个新漏洞。

这也是为什么“配置写入”的权限必须比“配置读取”严一个数量级：读配置最多泄露信息，写配置等于接管行为。管理端点的写操作应该单独审批、单独审计、单独限流。

## 四、漏洞二：Clientside LLM Credentials 的 SSRF

第二组问题和配置端点无关，出在一个“文档里写着的功能”上：**Clientside LLM Credentials**。

`/chat/completions` 等端点接受请求体里的 `user_config` 参数。带了这个参数，LiteLLM 会**用调用者提供的配置新建一个 Router**：

```python
elif "user_config" in data:
    router_config = data.pop("user_config")
    user_router = litellm.Router(**router_config)   # 完全由调用者控制
    ret_val = getattr(user_router, f"{route_type}")(**data)

```

这个配置里可以指定 `model_list` 和任意的 `api_base`。于是：

1. 调用者把 `api_base` 指向自己的服务器（原文用 `10.10.13.37:9999`）；
2. LiteLLM 按这个配置发请求；
3. 请求里带着**网关默认 header 中配置的 provider key**；
4. 攻击者的监听器收到 key。

原文特别指出：LiteLLM 有一个 `configurable_clientside_auth_params` 设置，用来限制请求体里直接传 `api_base` / `api_key`；但 `user_config` 走的是**另一条代码路径，完全绕过了这个限制**。

这是一个典型的 SSRF：**不是“读一个文件”，而是“让服务器带着你的凭据去访问我指定的地址”。** 除了偷 key，还可以用来探测内网。

## 五、附带的两个问题：哈希暴露与无盐单次哈希

原文还列了两项“不能直接拿下系统、但会显著放大风险”的问题：

- **密码哈希暴露**：低权限用户能通过 API 拿到其他账号的密码哈希。原本应该只有管理员能看。
- **不安全的哈希存储**：单次标准哈希、**不加盐**。相同密码产生相同哈希，可以直接查彩虹表；单次迭代也让暴力破解变得便宜。

两者都被 LiteLLM 标记为重复项（GHSA），原文也注明了这一点。单独看等级不高，但和前面的权限提升串起来，就是“先拿哈希、再离线破解、再用密码横向”。

## 六、CVE、版本与修复

| 编号                 | 问题                     | 说明                     |
| ------------------ | ---------------------- | ---------------------- |
| **CVE-2026-35029** | /config/update 缺少授权    | 任意已认证用户可改运行时配置         |
| **CVE-2026-59823** | user\_config 参数导致 SSRF | 可重定向请求、窃取 provider key |
| GHSA（重复项）          | 密码材料暴露                 | 低权限用户可读他人哈希            |
| GHSA（重复项）          | 无盐、单次哈希                | 离线破解成本低                |

- 研究版本：**LiteLLM v1.77.7**
- 修复版本：**v1.83.9**
- 披露方式：与维护者协调披露

## 七、这件事和“别把 API token 交给 LLM”的关系

表面上看，这是一篇网关漏洞分析；但放到“如何避免 API token 泄露”这个主题下，它给了三条更普遍的教训。

### 7.1 集中管理 = 集中风险

网关的卖点就是“key 只放在一处”。这句话反过来读就是：**那一处被攻破，所有 key 一起没**。所以网关的隔离等级、补丁节奏、管理面暴露面，必须按“它持有全部供应商凭据”来定，而不是按“一个普通内部服务”来定。

### 7.2 管理面和数据面必须分开

这次最严重的问题出在**管理面**（`/config/update`），而不是模型调用本身。管理面一旦和普通用户的 UI 会话共享同一套 token 分类逻辑，前缀匹配这种小细节就会变成权限提升。

判断标准很简单：**能改配置的端点，必须独立鉴权、独立网络位置、独立审计**，不能靠“用户登录了”来授权。

### 7.3 客户端提供的配置就是代码

`user_config` 看起来只是个参数，实际上让调用者决定“请求发到哪、带什么 header”。**任何允许调用者指定 `api_base`、`target`、模块路径、URL 的字段，都要按 SSRF / RCE 来审**。白名单不够，还要确认没有第二条代码路径绕过它——原文的 bypass 就是这么来的。

### 7.4 为什么“老问题”会反复出现在新组件里

缺授权、路径穿越、动态加载、弱密码存储，都是教科书里的老问题。它们之所以出现在一个 2026 年的 LLM 网关上，原因并不神秘：

- **先跑通，再补鉴权**：新组件的迭代节奏快，管理端点往往从“内部调试用”长成了“生产可用”，但鉴权模型没跟上；
- **复用 UI 会话**：为了省事，管理面直接复用普通用户的登录态，于是“能登录”被误当成“能管理”；
- **为灵活性开放客户端配置**：`user_config` 这类功能本来是为了让调用者自带模型和地址，但设计时没有把“谁有权指定目标”想清楚；
- **默认配置面向 demo**：开源项目的第一目标是“五分钟跑起来”，安全默认值通常排在后面。

原文的结论很直白：这些不是新型攻击，而是**基础安全又被当成了次要事项**。对使用者的含义是：不要因为组件“新”就假设它“安全”，网关要按带外管理设备的标准来对待。

## 八、对我们自己的含义

如果按我们前面讨论的形态——**本地 MCP 网关 + 云端 IR 编译服务**——这篇研究至少对应五条设计约束：

1. **编译服务不持有任何 LLM provider key。** 它只做 IR→SimaticML 的确定性转换，不需要调模型；一旦需要，也应该走独立网关、独立凭据、独立出网策略。
2. **管理面和数据面分离。** 改配置、发授权、看用量属于管理面，不能和普通用户的请求共享同一张 token、同一个端口、同一套前缀规则。
3. **不接受客户端指定 `api_base` / `target` / 模块地址。** 如果编译服务需要回调，目标地址必须来自服务端配置的白名单，而不是请求体。
4. **凭据不进工具参数、不进日志。** license token、云服务 token 用本地注入或短时凭据；日志里只留请求 id 和结果码。
5. **补丁 SLA 要按“钥匙串”定。** 网关类组件不能等季度更新；至少要有“安全版本发布后 72 小时内升级”的流程，以及升级前的出网收紧作为临时缓解。

再加一条运维层面的：**私有化部署时，管理面默认只监听内网/本机**，不要因为“反正内网”就把它暴露给整个网段。

### 8.1 上线前的网关自检

| 检查项      | 通过标准                            |
| -------- | ------------------------------- |
| 管理端点鉴权   | 独立角色检查，不依赖“已登录”                 |
| UI token | 不能调用管理端点；分类不依赖单一 team\_id       |
| 路由匹配     | 精确路径 / 方法，禁用无边界的 startswith     |
| 客户端配置    | 不接受 api\_base / target / 模块地址   |
| 出网       | 默认拒绝，只放行 provider 域名            |
| 凭据       | 按租户 / 环境隔离，短期、可轮换               |
| 日志       | 不含 key、prompt 原文、完整 provider 响应 |
| 版本       | ≥ 1.83.9，或有等价补丁                 |

### 8.2 怀疑被利用时先查什么

1. 最近的 `/config/update` 调用，尤其是来自非管理员账号的；
2. 环境变量快照：`UI_USERNAME`、`UI_PASSWORD`、`UI_LOGO_PATH` 是否被改过；
3. `/get_image` 是否读过非图片路径；
4. pass-through 端点里是否新增了 `s3://` / `gcs://` 目标；
5. 网关的出网日志里有没有未知 IP / 域名；
6. provider 账单和 key 使用记录有没有异常峰值；
7. 轮换顺序：先 provider key，再网关自身凭据，最后下游系统。

## 九、边界

- 研究针对 **LiteLLM v1.77.7**，修复版本 **v1.83.9**；如果你用的是更早或更晚的版本，先核对补丁状态，不要直接套结论。
- 本文**未复现**任何 PoC；代码片段是原文节选，为了可读性做了删减，**不要在生产环境照抄测试**。
- CVE / GHSA 的细节、影响范围与修复说明，以 NVD、GitHub Security Advisory 和 LiteLLM 官方公告为准；原文的 GHSA 编号在抓取中有字符损坏，本文未强行还原。
- LiteLLM 只是“LLM 网关”这一类组件的一个样本；文中三条教训（集中风险、管理面隔离、客户端配置即代码）对其他网关同样成立。
- 原文结论说得很直白：这些**不是新型攻击**，缺少授权、路径穿越、动态加载导致 RCE、弱密码存储，都是老问题；只是在一个快速演进的生态里，基础安全又被当成了次要事项。

---

**相关阅读**：[AI 基建 / 评测 / 安全完全指南](https://www.hubwiz.com/blog/ai-infra-stack/) ｜ [API Key 是怎么被 LLM 读走的](https://www.hubwiz.com/blog/llm-api-key-leakage-agent-context/) ｜ [Agent 安全：九起真实事故，六项通用控制](https://www.hubwiz.com/blog/agent-security-nine-incidents-six-controls/) ｜ [MCP 供应链体检](https://www.hubwiz.com/blog/mcp-registry-supply-chain-audit/) ｜ [开源模型后门：投毒 1% 就能让编码代理泄露 .env](https://www.hubwiz.com/blog/open-model-backdoor-codex-exfiltration/) ｜ [1–2 毫秒起的 microVM：Hyperlight 0.18](https://www.hubwiz.com/blog/hyperlight-018-ring-buffer-arm64-windows/)

**Sources**

- Check Point Research, *When LLM Gateways Go Wrong: Findings from LiteLLM*, 2026-10-13：[https://research.checkpoint.com/2026/when-llm-gateways-go-wrong-findings-from-litellm/](https://research.checkpoint.com/2026/when-llm-gateways-go-wrong-findings-from-litellm/?ref=hubwiz.com)
- CVE-2026-35029（`/config/update` 缺少授权）
- CVE-2026-59823（`user_config` 参数 SSRF）
- LiteLLM 官方仓库与安全公告：[https://github.com/BerriAI/litellm](https://github.com/BerriAI/litellm?ref=hubwiz.com)
- 对照阅读（本站）：[API Key 是怎么被 LLM 读走的](https://www.hubwiz.com/blog/llm-api-key-leakage-agent-context/)、[Agent 安全九起事故](https://www.hubwiz.com/blog/agent-security-nine-incidents-six-controls/)、[MCP 供应链体检](https://www.hubwiz.com/blog/mcp-registry-supply-chain-audit/)

*本文由 hubwiz 编译整理，转载请注明出处。*

> **这是「AI 基建 / 评测 / 安全」专题的一部分。**完整地图见：[AI 基建 / 评测 / 安全完全指南](https://www.hubwiz.com/blog/ai-infra-stack/)