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 个星标。
如何修复?
- 将服务器升级到 GPUStack 2.2.0 或更高版本:
pip install --upgrade "gpustack==2.2.0"
- 用新镜像重新创建容器。只拉取镜像而不重新创建容器,旧进程会继续运行。
- 确认回环捷径已消失。
force_auth_localhost标志在 2.2.0 中不起作用,每个请求都必须认证。 - 检查管理状态是否有你未做的更改。公告提到用户和模型更改、持久的管理员 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 的客户端无需代理即可到达同一分支。公告记录了两条路径。
如何被利用?
- 攻击者通过同主机反向代理向任意 API 端点发送未认证请求。
- 代理将请求转发到
127.0.0.1:<port>,因此 GPUStack 看到回环 TCP 对端。 get_current_user找不到凭据,调用client_ip_getter,得到127.0.0.1,并加载内置管理员账户。- 攻击者现在以管理员身份行动。公告列出用户和模型更改、读取 worker 和云提供商秘密,以及创建具有
*范围的持久管理员 API 密钥。 - 使用管理员密钥,攻击者调用管理员专用端点
POST /v2/inference-backends/from-yaml并提供后端定义。该端点用于运行推理后端,公告称攻击者用它在 worker 主机上运行反向 shell。 - 攻击者保留管理员 API 密钥,创建后从任何来源地址都有效。删除代理规则不会撤销它。
厂商称在 v2.1.1 部署上观察到这种真实环境利用,攻击者创建的自定义后端运行了反向 shell。公告未给出观察日期或受害者数量。
攻击如何展开?
反向代理把攻击者的网络请求变成回环请求,回环检查又把它变成管理员会话。
无凭据"] --> 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 发布,包含真实环境利用报告。
常见问题
利用已被确认吗?
是。厂商公告称该漏洞“已在 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 日检查)