flawopen.com/Incidents/solarwinds-sunburst-build-compromise-december-2020
SUNBURST:当构建流水线本身就是漏洞
SUNSPOT 在构建时篡改 Orion 源码,将 SUNBURST 植入已签名更新。有效签名只能证明来源,不能证明代码完整性。
想象一家值得信赖的锁匠公司,定期给成千上万栋楼寄去封好口、盖了章的维护套件。有人闯进组装套件的工厂,在封口和盖章之前往每套里多放了一个小零件。每栋楼收到的套件确实来自这家锁匠,于是照常安装。封条从未被破坏,因为篡改发生在封装之前。
核心概念与专有名词
SUNSPOT- CrowdStrike 分析过的构建服务器植入程序。它监视 MSBuild.exe 编译 Orion 的时刻,并在构建期间把一个源文件替换成带后门的版本。
SUNBURST- 被编译进 SolarWinds.Orion.Core.BusinessLayer.dll 的后门。它会潜伏最长两周,然后通过 HTTP 联系攻击者的基础设施。
代码签名- 证明二进制文件来自密钥持有者的加密签名。它无法说明产出该文件的构建过程是否被篡改。
可复现构建- 用同一份源码构建出逐位相同结果的构建方式,便于相互独立的构建机互相校验结果。
CWE-506- 嵌入式恶意代码。本案中,恶意代码是在厂商自己的构建过程中被插入的,而不是在源码仓库里。
框架映射
- 本指南所述弱点的 CWE 参考: CWE-506 CWE-829
- D3-OTF 出站流量过滤 — 本指南中的出站网络限制会限制服务或主机可访问的目标。
事件演进时间线
攻击者进入 SolarWinds 的环境;9 月 12 日,他们向一次 Orion 构建注入测试代码,以验证手法可行。
SUNBURST 借助 SUNSPOT 被编译进 Orion 构建。
SolarWinds 开始发布在不知情中含有 SUNBURST 的 Orion 热修复;受影响版本包括 2019.4 HF5 和 2020.2。
攻击者把 SUNSPOT 从构建环境中移除。
FireEye 发布对 SUNBURST 的分析,CISA 发布第 21-01 号紧急指令,要求联邦机构断开 Orion。
攻击执行流程分解
构建服务器被攻陷
攻击者进入 SolarWinds 的构建环境,在编译 Orion 的服务器上安装了 SUNSPOT。
构建途中替换源文件
SUNSPOT 一旦发现 MSBuild.exe 在构建 Orion 解决方案,就把 InventoryManager.cs 换成带后门的版本,编译结束后再换回原文件。
真正的密钥为结果签名
编译出的 SolarWinds.Orion.Core.BusinessLayer.dll 照常经过签名步骤,因此带后门的 DLL 带有有效的 SolarWinds 签名。
客户安装受信任的更新
安装受影响更新的客户不到 18,000 家。随后,攻击者从中挑选了规模小得多的一批受害者继续入侵。
源代码对比:漏洞与安全实现
示意性的 C# 代码,并非 SolarWinds 的流水线。有漏洞的任务信任单一、长期运行的构建服务器,并为其产出的任何东西签名,这正是 SUNSPOT 所利用的。加固后的关卡要求在全新部署、分别管理的两台构建机上构建同一个已批准的提交,以恒定时间比较两者的 SHA-256 摘要,只要有任何差异就拒绝签名。这样,一台构建机上的植入程序只会造成结果不一致,而不是一个已签名的发布;只有攻击者同时控制两台构建机时才会失效,因此它们绝不能共享凭据或镜像。
using System;
using System.Diagnostics;
public static class ReleasePipeline
{
// 构建在一台服务器上使用共享且可写的工作副本,并对
// 任何构建结果签名。构建期间对源码树的修改也会被签名。
public static void BuildAndSign(string project, string outputDll)
{
using var build = Process.Start("msbuild.exe", $"\"{project}\" /p:Configuration=Release");
build!.WaitForExit();
if (build.ExitCode != 0)
throw new InvalidOperationException("build failed");
Signer.Sign(outputDll); // HSM-backed signing service client
}
}using System;
using System.IO;
using System.Security.Cryptography;
public static class ReleaseGate
{
// 两个隔离的构建器从全新、只读的检出中编译同一个固定 commit,
// 并使用 /p:Deterministic=true。门禁只对完全相同的输出签名。
public static void VerifyAndSign(string builderAOutput, string builderBOutput,
string builtCommit, string approvedCommit)
{
if (!string.Equals(builtCommit, approvedCommit, StringComparison.Ordinal))
throw new InvalidOperationException("build is not from the approved commit");
byte[] a = SHA256.HashData(File.ReadAllBytes(builderAOutput));
byte[] b = SHA256.HashData(File.ReadAllBytes(builderBOutput));
if (!CryptographicOperations.FixedTimeEquals(a, b))
throw new InvalidOperationException("independent builds differ; refusing to sign");
Signer.Sign(builderAOutput, Convert.ToHexString(a)); // digest is logged with the signature
}
}工程与系统安全加固清单
- ✓像保护生产环境一样保护 CI 运行器:它们保存源码、机密和签名密钥,并产出客户要运行的东西,因此应享有生产数据库级别的隔离、访问控制和审计日志。
- ✓在全新部署、分别管理的构建机上用确定性编译把每个发布构建两次,只要产物摘要不同就让发布失败。
- ✓不要把有效签名当作安全证明;它证明的是来源,而来源的可信度取决于保管密钥的环境。
- ✓限制特权代理的出站流量。SUNBURST 必须连到它的命令与控制服务器,因此能与任意互联网主机通信的监控工具应当是经过明确评审的决定。
- ✓按照“若被完全攻陷攻击者能得到什么”来确定每个厂商代理的权限,而不是按部署是否方便。
- ✓保留攻击者无法修改的遥测:只有存放在主机之外、只能追加的日志,才能在事后还原长达数月的入侵。