一、CI/CD 的安全边界不在一台服务器上
GitLab 常被当作代码托管平台,但在实际企业环境中,它更像一个交付控制平面:
开发者身份
↓
代码仓库与合并审批
↓
GitLab CI 调度
↓
Runner 执行
↓
制品库 / 镜像仓库
↓
测试、预生产与生产环境
每一条箭头都代表一种信任:令牌、签名、网络身份、审批结论或制品来源。
当 GitLab 出现未认证文件读取,风险并不天然覆盖所有下游;但如果控制平面保存了能跨越这些边界的长期秘密,攻击者就可能把"读文件"变成"借合法身份操作"。
二、CVE-2026-85706 的事实范围
【已确认事实】 GitLab 于 2026 年 9 月 10 日修复 CVE-2026-85706。漏洞位于仓库提交 API,涉及路径限制不当和身份验证缺失,未认证攻击者在特定条件下可读取任意文件。
修复版本:
-
19.1.8;
-
19.2.6;
-
19.3.2。
影响 18.7 起相应分支的更早版本。CISA 于 9 月 11 日将其加入 KEV,确认存在现实利用。
【证据边界】 官方没有把漏洞描述为直接 RCE、直接制品篡改或直接生产接管。本文讨论的是在额外凭据和架构条件成立时,风险如何沿 CI/CD 信任链扩散。
三、攻击链能否成立,取决于五个"如果"
如果一:服务进程可以读取秘密
文件读取能力受 GitLab 运行身份、容器挂载和文件权限限制。如果敏感凭据不在可读范围,攻击链会在第一步中断。
如果二:秘密仍长期有效
短期令牌、受众限制和来源绑定可以缩小窗口。长期静态 Token 或私钥则可能在补丁后继续有效。
如果三:秘密拥有下游写权限
只读监控令牌与能够推送镜像、修改流水线或部署生产的凭据,风险完全不同。
如果四:下游相信身份,不验证来源
如果制品库只检查"令牌是否有效",却不检查产物是否来自受信流水线,盗用令牌便可能绕过流程。
如果五:关键动作没有独立审批
当代码合并、构建、签名和生产部署都由同一身份域控制,上游被突破后就缺少第二道阻断。
因此,完整条件链是:
文件可读
∧ 读到有效秘密
∧ 秘密有下游写权限
∧ 下游缺少来源校验
∧ 关键动作缺少独立审批
= 供应链影响可能扩大
四、四个最容易被撬动的信任点
1. Runner 身份
Runner 往往能读取 CI 变量、访问缓存和制品,并连接构建或部署环境。高风险设计包括:
-
共享 Runner 持有跨项目永久凭据;
-
Runner 直接使用生产云 Access Key;
-
外部合并请求可触达高权限执行器;
-
缓存和工作目录跨项目复用。
更安全的方向是临时执行器、项目隔离、短期工作负载身份和受保护分支限制。
2. 制品与镜像身份
标签可以移动,文件名可以相同,真正可靠的是内容摘要、签名和来源证明。
如果发布系统只相信"来自某个仓库账号的推送",凭据被盗后就可能接受非预期制品。应要求:
-
制品以摘要固定;
-
发布产物经过独立签名;
-
签名身份与受信流水线绑定;
-
部署端验证签名和来源,而不是只验证仓库权限。
3. 云部署身份
长期云密钥存放在 GitLab 或 Runner 文件系统,是最典型的信任集中。
更合理的方式是让流水线通过 OIDC 等机制获得短期身份,并限制:
-
允许的项目和分支;
-
令牌受众;
-
可扮演的角色;
-
生存时间;
-
可执行的云 API。
4. 审批与发布身份
如果同一个 GitLab 管理员既能修改代码保护规则、又能改变流水线、还能触发生产发布,那么技术上存在单点信任。
关键发布应引入独立控制面,例如生产环境审批、独立签名服务或部署平台策略。
五、一个无害模型:信任隔离如何截断扩散
下面的模型不连接任何真实系统,只展示策略判断:
def can_deploy(token_valid, artifact_signed,
provenance_ok, independent_approval):
return all([
token_valid,
artifact_signed,
provenance_ok,
independent_approval,
])
print(can_deploy(True, False, False, False)) # False
print(can_deploy(True, True, True, True)) # True
即使攻击者获得一个有效令牌,只要部署端仍要求可信签名、来源证明和独立审批,单个身份泄露就无法直接完成生产发布。
这就是"零信任 CI/CD"的核心:不把上游认证成功等同于下游所有动作都可信。
六、从漏洞响应到 CI/CD 对账
升级 GitLab 后,应把以下对象串成一条可验证记录:
提交 SHA
→ 合并审批
→ 流水线身份与配置
→ Runner 身份
→ 构建材料
→ 制品摘要
→ 签名与来源证明
→ 镜像摘要
→ 部署审批
→ 生产版本
任何断点都需要解释。
重点排查:
-
不对应可信提交的流水线;
-
流水线配置或变量异常变化;
-
新增 Runner 或异常执行节点;
-
制品摘要、签名和来源证明不一致;
-
镜像标签指向发生变化;
-
发布或部署缺少正常审批;
-
云审计出现异常身份使用。
七、补丁与架构改造要并行
补丁解决入口
-
升级 GitLab 到 19.1.8、19.2.6、19.3.2 或更高安全版本;
-
运行官方检测规则;
-
对可能暴露的秘密执行分级轮换;
-
验证数据库迁移和所有节点版本一致。
架构改造降低爆炸半径
-
GitLab、Runner、Registry 与生产环境分属不同身份域;
-
静态凭据改为短期工作负载身份;
-
Runner 不直接持有跨项目或生产永久权限;
-
制品必须签名并附带可验证来源;
-
生产部署需要独立审批和策略校验;
-
关键操作和秘密使用进入统一审计。
漏洞修复关注"这个入口还通不通",架构治理关注"下一个入口被突破时能走多远"。
八、DevSecOps 团队的权限矩阵
可以用下面的最小矩阵检查信任是否集中:
| 主体 | 读源码 | 改流水线 | 取秘密 | 推制品 | 部署生产 |
|---|---|---|---|---|---|
| 普通开发者 | 是 | 受审查 | 否 | 否 | 否 |
| 受保护流水线 | 是 | 固定配置 | 短期 | 是 | 否 |
| 发布流水线 | 指定版本 | 否 | 短期 | 签名发布 | 需审批 |
| 部署控制面 | 否 | 否 | 环境身份 | 验证制品 | 是 |
| GitLab 管理员 | 管理需要 | 管理需要 | 不应直接获取生产秘密 | 否 | 否 |
【建议】 如果某一行同时拥有修改代码、修改流水线、读取长期秘密和部署生产的能力,应优先拆分。
九、如何验证制品仍然可信
1. 从可信源码重建
在隔离环境中固定依赖和构建参数,重新生成关键制品,比较摘要和可复现结果。
2. 验证签名与来源
确认签名身份、签名时间、构建平台和输入提交均符合发布策略。
3. 检查仓库与注册表事件
关注异常推送、标签移动、删除、覆盖和权限变更。
4. 将部署记录与流水线对账
生产版本应能回溯到唯一制品摘要、可信流水线和经过审批的提交。
【边界说明】 需要进行多深的重建取决于是否存在可信命中、日志缺口和凭据暴露证据。并非所有受影响实例都必须无差别重建全部制品。
十、P0---P2 落地清单
P0:控制当前事件
-
升级全部自托管 GitLab 节点;
-
保全日志并运行官方检测规则;
-
收紧公网入口;
-
枚举 GitLab 进程可读的秘密和挂载;
-
对可信命中启动凭据响应。
P1:确认交付链完整性
-
检查 Runner 注册、流水线和变量变化;
-
核验关键制品、镜像摘要与签名;
-
将发布部署与提交、审批记录对账;
-
轮换高风险凭据并撤销旧值;
-
监控旧身份继续使用的行为。
P2:重构信任边界
-
使用 OIDC 等短期身份连接云环境;
-
按项目和环境隔离 Runner;
-
构建、签名和部署使用不同身份;
-
强制制品签名与来源证明;
-
生产部署引入独立审批策略;
-
定期演练代码平台失陷场景。
十一、不要把这些推断写成事实
"任意文件读取等于已经投毒制品"
错误。还需要读到有效身份、拥有写权限并绕过下游验证。
"CVSS 10.0 等于直接 RCE"
错误。官方披露的直接能力是文件读取,不能省略后续条件。
"没有发现异常流水线就没有泄露"
错误。攻击者可能仅收集秘密,延后或在下游使用。
"轮换 GitLab 密钥就覆盖所有下游"
错误。Runner、云平台、制品库和第三方集成可能各自维护独立凭据。
总结
CVE-2026-85706 之所以值得从 CI/CD 架构角度审视,不是因为文件读取必然导致供应链入侵,而是因为它暴露了一个长期存在的问题:太多交付系统把信任集中在 GitLab 和少数静态秘密上。
成熟的软件供应链应能做到:即使代码平台的一个边界被突破,攻击者仍无法凭单个令牌修改制品、伪造来源并部署生产。补丁解决今天的漏洞,信任隔离决定下一次漏洞的爆炸半径。