3. 流水线威胁建模与风险分析

流水线安全不是“加几个扫描器”就能解决的问题。CI/CD 系统本身是一个高权限、强自动化、跨信任边界的分布式系统:它同时持有源代码、构建凭证、镜像仓库令牌、云平台部署密钥和生产环境入口。攻击者一旦在流水线中取得一个执行点,往往可以借助流水线自身的权限横向移动到制品仓库、生产集群甚至客户环境。因此,在写任何安全策略之前,必须先做威胁建模与风险分析,把“谁会攻击、从哪里进、能拿到什么、会造成什么后果”讲清楚。

一、用 STRIDE 对流水线组件做系统化威胁枚举

STRIDE 是微软提出的威胁分类框架,包含六类威胁:Spoofing(仿冒)、Tampering(篡改)、Repudiation(抵赖)、Information Disclosure(信息泄露)、Denial of Service(拒绝服务)、Elevation of Privilege(权限提升)。它适合对流水线的每个组件逐一提问,而不是笼统地说“流水线不安全”。

以一条典型的 GitLab CI 流水线为例,可以按组件建立 STRIDE 矩阵:

组件STRIDE 威胁示例典型后果
Git 仓库Spoofing:伪造提交者身份;Tampering:篡改受保护分支恶意代码进入主干
Runner 执行器Elevation:利用特权容器逃逸;Tampering:污染构建缓存宿主机被控,横向渗透
CI 变量/密钥Information Disclosure:日志打印密钥;Spoofing:伪造 OIDC 身份云凭证泄露
制品仓库Tampering:覆盖已发布镜像;Repudiation:无签名无法追溯供应链投毒
部署目标Elevation:越权部署到生产;DoS:删除服务生产事故或数据泄露

做 STRIDE 时不要只列威胁,还要为每条威胁标注“现有控制”和“残余风险”。例如“CI 变量泄露”的现有控制可能是“变量设为 masked 且 protected”,残余风险是“masked 只对日志生效,脚本仍可外发”,因此需要补充“禁止在 MR 流水线使用生产密钥”的策略。

二、攻击树:把“攻击者如何达成目标”拆成可验证的路径

攻击树以攻击者的最终目标为根节点,向下分解为若干子目标,子目标之间用 AND/OR 连接。AND 表示必须同时满足,OR 表示任选其一。它比 STRIDE 更适合分析“具体怎么被打穿”。

以“向生产镜像注入后门”为根目标,可构造如下攻击树:

  • 根目标:生产镜像包含攻击者代码
    • OR 路径 A:污染源代码
      • AND:获得仓库写权限 + 绕过分支保护 + 通过代码评审
    • OR 路径 B:污染构建过程
      • OR:篡改构建脚本 / 污染依赖包 / 劫持构建缓存
    • OR 路径 C:污染制品
      • AND:获得镜像仓库推送权限 + 覆盖已有 tag

攻击树的价值在于:每条叶子节点都可以对应一个可落地的控制措施。路径 A 对应“分支保护 + 强制评审 + 提交签名”;路径 B 对应“依赖锁定 + 缓存隔离 + 构建脚本只读”;路径 C 对应“镜像签名 + tag 不可变 + 推送权限最小化”。这样威胁建模就从“描述风险”变成了“分配控制”。

CI/CD 攻击面与攻击树示意 根目标:生产镜像含后门 路径A:污染源代码 AND:仓库写权限 + 绕过分支保护 + 通过代码评审 路径B:污染构建过程 OR:篡改构建脚本 / 污染依赖包 / 劫持构建缓存 路径C:污染制品 AND:镜像仓库推送权 + 覆盖已有 tag 控制措施 分支保护 + 强制评审 + 提交签名 控制措施 依赖锁定 + 缓存隔离 + 构建脚本只读 控制措施 镜像签名 + tag 不可变 + 推送权限最小化 CI/CD 主要攻击面:源代码入口 · 构建执行环境 · 依赖与缓存 · 密钥与凭证 · 制品仓库 · 部署通道 每个攻击面都需对应“身份认证 + 权限最小化 + 完整性校验 + 审计追溯”四类控制

三、CI/CD 攻击面识别:从入口到出口逐段排查

攻击面识别要回答“哪些地方可以被外部输入影响”。CI/CD 的攻击面通常集中在六个位置:

  1. 源代码入口:Pull Request、分支推送、Webhook 触发。风险点是攻击者可通过 fork 仓库提交恶意 PR,触发流水线执行。
  2. 构建执行环境:Runner、构建容器、宿主机。风险点是特权容器逃逸、共享 Runner 被其他项目污染。
  3. 依赖与缓存:npm、PyPI、Maven、Docker 基础镜像、构建缓存。风险点是依赖混淆、typosquatting、缓存投毒。
  4. 密钥与凭证:CI 变量、OIDC token、SSH key、云 AK/SK。风险点是日志泄露、脚本外发、权限过大。
  5. 制品仓库:镜像仓库、包仓库。风险点是 tag 覆盖、未签名制品、仓库权限过宽。
  6. 部署通道:Kubernetes、云平台、配置管理。风险点是越权部署、配置篡改、回滚通道被利用。

识别时建议用“数据流 + 信任边界”的方法:画出从代码提交到生产部署的完整数据流,标出每一次跨信任边界的跳转。例如“MR 流水线 → 生产密钥”就是一条不应存在的跨边界路径,必须通过环境隔离和权限分离切断。

四、典型供应链攻击案例与缓解策略

案例一:SolarWinds(2020)。攻击者入侵构建环境,在 Orion 平台的合法更新包中植入后门,通过官方更新通道分发给约 18000 个客户。根因是构建环境缺乏完整性校验和构建过程审计。缓解策略:构建环境隔离、构建产物签名、SBOM 生成与比对、构建日志不可篡改。

案例二:Codecov Bash Uploader(2021)。攻击者修改了 Codecov 的 Bash 上传脚本,使其在 CI 环境中窃取环境变量并外发。根因是脚本以 curl | bash 方式执行且未做完整性校验。缓解策略:固定脚本版本与哈希、禁止 curl | bash、对 CI 变量做最小化与轮换。

案例三:Log4Shell(2021)。Log4j 的 JNDI 注入允许远程加载恶意类,影响面覆盖大量使用该库的构建与运行环境。缓解策略:依赖清单(SBOM)快速定位受影响组件、依赖版本锁定、构建时依赖扫描、运行时 WAF 与出站限制。

案例四:xz-utils 后门(2024)。攻击者通过长期贡献获取维护者信任,在压缩库中植入后门,影响 SSH 认证链路。根因是开源依赖的维护者信任模型被滥用。缓解策略:关键依赖人工复核、构建可复现、二进制与源码一致性校验、对上游变更做异常检测。

供应链攻击的共同特征是:攻击者不直接攻击你的应用,而是攻击你信任的构建过程、依赖或分发通道。因此,缓解策略的核心不是“更努力地扫描”,而是“减少信任假设 + 增加可验证性”。

五、把威胁建模落到流水线策略上

威胁建模的产出不应停留在文档。建议把每条威胁映射为一条可执行的流水线策略,并写入配置。例如:

# 示例:GitLab CI 中限制生产密钥仅用于受保护分支
deploy_prod:
  stage: deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
  variables:
    PROD_DEPLOY_TOKEN: $PROD_DEPLOY_TOKEN   # protected + masked
  script:
    - cosign verify --key cosign.pub $IMAGE  # 部署前校验镜像签名
    - kubectl apply -f k8s/prod/

这段配置体现了三个控制:规则限制触发条件、变量受保护、部署前验签。它对应的正是攻击树中“路径 C:污染制品”的缓解措施。威胁建模只有落到这种可检查、可审计的配置上,才真正降低了风险。

最后,建议把 STRIDE 矩阵、攻击树和控制映射表纳入版本管理,每次流水线架构变更时同步更新。威胁建模不是一次性活动,而是随流水线演进而持续维护的安全资产。