我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

GPUStack 认证绕过导致远程代码执行,已在野利用 (CVE-2026-102895)

GPUStack 2.2.0 之前信任回环来源,因此同主机反向代理会把远程请求变成管理员访问和代码执行。

TL;DR

  • GPUStack 2.2.0 之前把看起来来自 127.0.0.1 的请求当作内置管理员,而同主机反向代理让每个远程请求都看起来像回环。未认证攻击者因此获得完整管理员权限。
  • 所有低于 2.2.0、使用默认 force_auth_localhost=false 且位于同主机反向代理后的部署都受影响。公告还提到内嵌网关模式下伪造 X-GPUStack-Real-IP 头的路径。
  • 厂商报告在 2.1.1 部署上发现真实环境利用,攻击者创建了运行反向 shell 的推理后端。CVE 记录于 2026 年 11 月 4 日发布。截至 2026 年 11 月 5 日,它不在 CISA KEV 中。
  • 升级到 GPUStack 2.2.0 或更高版本并重新创建容器。旧版本上可设置 force_auth_localhost=true,并在代理处删除客户端提供的 X-GPUStack-Real-IP。参见“如何修复?”。

我受影响吗?

以下条件全部成立时,你才会受影响:

  • 你的 GPUStack 服务器运行低于 2.2.0 的版本。
  • force_auth_localhost 为假或未设置,这是这些版本的默认值。
  • API 可通过配置了 proxy_pass http://127.0.0.1:<port> 的同主机反向代理访问,因此每个远程请求都以 TCP 对端 127.0.0.1 到达 GPUStack。在内嵌网关模式下,能设置 X-GPUStack-Real-IP: 127.0.0.1 的攻击者会到达同一分支。

反向代理这一条件是公告所称的典型自托管部署:同主机反向代理配合 proxy_pass http://127.0.0.1:<port>。这正是易受攻击分支所信任的部署结构。

分支 受影响版本 修复版本
2.1.x 及更早 2.1.1 及所有更早版本 2.2.0,2026-06-18 发布
2.2.x 无(已在 2.2.0 修复) 持续更新到最新版本

检查你运行的内容:

# Version inside the server container
docker exec gpustack gpustack version

# Python package version
docker exec gpustack pip show gpustack | head -2

# Is the loopback bypass forced off?
docker exec gpustack env | grep -i FORCE_AUTH_LOCALHOST

最后一条命令在设置为默认值时不输出任何内容。该设置也出现在服务器配置文件的 force_auth_localhost 中。

GPUStack 是一个开源 GPU 集群管理器,用于 AI 模型服务和 GPU 实例供应。2026 年 11 月 5 日,其 Docker 镜像在 Docker Hub 上被拉取 942,824 次,仓库有 5,978 个星标。

如何修复?

  1. 将服务器升级到 GPUStack 2.2.0 或更高版本:
pip install --upgrade "gpustack==2.2.0"
  1. 用新镜像重新创建容器。只拉取镜像而不重新创建容器,旧进程会继续运行。
  2. 确认回环捷径已消失。force_auth_localhost 标志在 2.2.0 中不起作用,每个请求都必须认证。
  3. 检查管理状态是否有你未做的更改。公告提到用户和模型更改、持久的管理员 API 密钥、worker 和云提供商秘密,以及通过管理员专用端点创建的推理后端。

对于暂时无法升级的部署,公告给出三种临时措施。它们降低暴露但不移除易受攻击的代码:

  • 设置 force_auth_localhost=true,使回环请求也需要认证。
  • 在反向代理或网关处删除任何客户端提供的 X-GPUStack-Real-IP 头,并确保代理不能使外部流量看起来像回环。
  • 将 API 放在不可信网络之外,置于 VPN 或边缘认证之后。

第一种措施对常见的反向代理形态有效。另外两种限制第二条路径和暴露窗口。

根本原因是什么?

根本原因是基于请求网络来源的认证决定。本地套接字对端或客户端提供的头不是身份,而同主机反向代理使对端地址失去意义。

以下是 v2.1.1 中 gpustack/api/auth.py 的易受攻击代码。第 51 至 56 行的辅助函数只在内嵌网关模式下返回 X-GPUStack-Real-IP 头,否则返回套接字对端:

def client_ip_getter(request: Request) -> str:
    if request.app.state.server_config.gateway_mode == GatewayModeEnum.embedded:
        return request.headers.get("X-GPUStack-Real-IP", "")
    else:
        return request.client.host

第 89 至 92 行的认证依赖随后在标志为假时把回环来源提升为第一个管理员:

if user is None and client_ip_getter(request=request) == "127.0.0.1":
    if not server_config.force_auth_localhost:
        user = await User.first_by_field(session, "is_admin", True)

该分支只在 basic、cookie、bearer 和 API 密钥检查都未能产生用户之后运行。完全没有凭据时,user 为 None,对代理请求 client_ip_getter 返回 127.0.0.1,查询返回内置管理员。代码从不检查 HTTP 方法、路径或调用方。

同一辅助函数在内嵌网关模式下信任头。设置 X-GPUStack-Real-IP: 127.0.0.1 的客户端无需代理即可到达同一分支。公告记录了两条路径。

如何被利用?

  1. 攻击者通过同主机反向代理向任意 API 端点发送未认证请求。
  2. 代理将请求转发到 127.0.0.1:<port>,因此 GPUStack 看到回环 TCP 对端。
  3. get_current_user 找不到凭据,调用 client_ip_getter,得到 127.0.0.1,并加载内置管理员账户。
  4. 攻击者现在以管理员身份行动。公告列出用户和模型更改、读取 worker 和云提供商秘密,以及创建具有 * 范围的持久管理员 API 密钥。
  5. 使用管理员密钥,攻击者调用管理员专用端点 POST /v2/inference-backends/from-yaml 并提供后端定义。该端点用于运行推理后端,公告称攻击者用它在 worker 主机上运行反向 shell。
  6. 攻击者保留管理员 API 密钥,创建后从任何来源地址都有效。删除代理规则不会撤销它。

厂商称在 v2.1.1 部署上观察到这种真实环境利用,攻击者创建的自定义后端运行了反向 shell。公告未给出观察日期或受害者数量。

攻击如何展开?

反向代理把攻击者的网络请求变成回环请求,回环检查又把它变成管理员会话。

flowchart LR A["攻击者请求
无凭据"] --> B["同主机反向代理
对端127.0.0.1"] B --> C["get_current_user
未找到用户"] C --> D["client_ip_getter返回回环
无身份检查"] D --> E["加载内置管理员"] E --> F["创建管理员API密钥
范围*"] F --> G["从YAML创建推理后端
在worker上反向shell"]

攻击者能做什么?

影响是完全控制 GPUStack 部署的管理权限,以及在其管理的 worker 主机上执行代码。

公告列出未认证攻击者可以采取的管理操作:

  • 枚举并修改用户、模型、worker 和配置。
  • 创建具有 * 范围的持久管理员 API 密钥,创建后从任何来源 IP 都有效。
  • 读取 worker 凭据和云提供商秘密。
  • 调用管理员专用 POST /v2/inference-backends/from-yaml 在 worker 主机上执行代码。

攻击者需要通过网络经代理访问 API。公告称在 2.1.1 部署上观察到利用,攻击者创建的后端运行了反向 shell。这是 worker 主机上的代码执行,而不只是 GPUStack 服务器。

API 仅可从可信网络访问的部署,对该网络上的任何人仍然暴露,包括被入侵的工作站或相邻租户。

为什么修复有效?

修复完全删除回环捷径,因此没有任何请求因来源而被认证。它还要求先通过网关令牌认证,才信任 X-GPUStack-Real-IP 头。

2.2.0 在 gpustack/api/auth.py 中的更改删除了提升回环调用者的三行:

@@ async def get_current_user(...)
             if token is not None:
                 user, api_key = await get_user_from_api_token(session, token)

-        if user is None and client_ip_getter(request=request) == "127.0.0.1":
-            if not server_config.force_auth_localhost:
-                user = await User.first_by_field(session, "is_admin", True)
         if user:
             if not user.is_active:
                 raise UnauthorizedException(message="User account is deactivated")

同一版本重写 client_ip_getter,只有在 gateway_token_auth 成功后才会读取转发头:

 def client_ip_getter(request: Request) -> str:
-    if request.app.state.server_config.gateway_mode == GatewayModeEnum.embedded:
-        return request.headers.get("X-GPUStack-Real-IP", "")
-    else:
-        return request.client.host
+    if request.app.state.server_config.gateway_mode != GatewayModeEnum.disabled:
+        try:
+            gateway_token_auth(request)
+            real_ip = request.headers.get("X-GPUStack-Real-IP")
+            if real_ip:
+                return real_ip
+        except UnauthorizedException:
+            pass
+    return request.client.host if request.client else ""

修复对这个绕过是完整的。它不会撤销攻击者在升级前创建的 API 密钥,也不会清理运行过攻击者提供后端的 worker 主机。这些需要“如何检查是否已遭入侵?”中的检查。

如何检查是否已遭入侵?

  • 在 GPUStack 界面列出管理员用户和 API 密钥。不是你创建的管理员或密钥是线索;在你的变更流程之外创建的 * 范围密钥是强证据。
  • 列出推理后端并与自己的记录比较。公告的利用路径通过 POST /v2/inference-backends/from-yaml 创建后端,厂商观察到从中发起的反向 shell。
  • 在 worker 主机上检查推理后端进程启动的 shell 的出站连接和进程树。打开到外部地址的交互连接的后端是线索。
  • 在 GPUStack 服务器和代理日志中搜索没有认证头却成功的 API 请求。易受攻击分支返回正常的管理响应,因此无凭据的成功请求是要找的模式。
  • 将 worker 和云提供商凭据视为已读取。轮换它们,并检查云账户中不是你创建的资源。

公告没有给出入侵指标或受害者数量。在到达易受攻击分支之前失败的请求可能不留痕迹。

时间线

  • 2026-05-19:修复提交“fix: remove localhost auto-admin bypass”进入仓库。
  • 2026-06-18:GPUStack 2.2.0 发布,包含修复。
  • 2026-07-31:厂商发布公告 GHSA-gj9m-f8ph-pmhr。
  • 2026-09-29:CVE 记录被预留,公告更新。
  • 2026-11-04:CVE-2026-102895 发布,包含真实环境利用报告。

从 2026 年 5 月修复提交到 2026 年 11 月 CVE 记录的时间线
图 1:修复和 2.2.0 发布比 CVE 记录早数月,因此升级窗口在记录出现前很久就已打开。来源:GPUStack 提交、版本和公告,2026 年 11 月 5 日检查

常见问题

利用已被确认吗?

是。厂商公告称该漏洞“已在 v2.1.1 部署上被观察到真实环境利用”,攻击者创建的自定义后端运行了反向 shell。公告未给出日期、受害者数量或指标。

默认配置受影响吗?

受影响,前提是 API 前有同主机反向代理。force_auth_localhost 默认为假,代理使每个远程请求的 TCP 对端为 127.0.0.1。没有同主机代理的直接部署不会走回环路径,除非内嵌网关模式信任伪造头。

公告的临时措施能完全修复吗?

不能。设置 force_auth_localhost=true 会在受影响版本上关闭回环分支,这是主要路径。升级会删除分支和对头的信任,因此是持久修复。

为什么 CVSS 评分是 9.8?

CVE 记录评分为 CVSS v3.1 9.8,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H。攻击者不需要特权或交互,对机密性、完整性和可用性的影响都很高。

它在 CISA KEV 中吗?

不在。截至 2026 年 11 月 5 日,CVE-2026-102895 不在 CISA 的 Known Exploited Vulnerabilities 目录中。厂商关于真实环境利用的声明是利用状态的来源。

升级会撤销攻击者创建的密钥吗?

不会。升级关闭绕过,但保留现有用户和 API 密钥。升级后撤销未知的管理员密钥,并轮换 worker 和云凭据。

参考文献

  • GPUStack 公告 GHSA-gj9m-f8ph-pmhr:https://github.com/gpustack/gpustack/security/advisories/GHSA-gj9m-f8ph-pmhr(2026 年 11 月 5 日检查)
  • CVE-2026-102895 记录:https://www.cve.org/CVERecord?id=CVE-2026-102895(2026 年 11 月 5 日检查)
  • GPUStack 修复提交 53f42a6a:https://github.com/gpustack/gpustack/commit/53f42a6a553542974396d16a4ff6b12bcbedb48d(2026 年 11 月 5 日检查)
  • GPUStack v2.2.0 版本:https://github.com/gpustack/gpustack/releases/tag/v2.2.0(2026 年 11 月 5 日检查)
  • v2.1.1 的 GPUStack 源代码,gpustack/api/auth.py:https://github.com/gpustack/gpustack/blob/v2.1.1/gpustack/api/auth.py(2026 年 11 月 5 日检查)
  • GPUStack 仓库:https://github.com/gpustack/gpustack(2026 年 11 月 5 日检查)
  • Docker Hub,gpustack/gpustack:https://hub.docker.com/r/gpustack/gpustack(2026 年 11 月 5 日检查)