GitHub Actions安全强化

概述

本文主要解释对于GitHub Actions的部分特性,如何加强安全。

使用密码

敏感的数字不应该在工作流中明文存储,而应该使用密码。密码可以配置在组织,仓库,或者环境变量中,从而允许你在Github中存储敏感信息。

密码使用Libsodium sealed boxes技术,所以在接触Github前就已经被加密。当通过UI或者REST API提交时,密码就已经被加密了。这种在客户端加密的方式极大的减小了再登陆github时的泄露风险。一旦密码被上传后,Github可以解密密码,从而在工作流中使用。

  • 不要使用结构化数据当做密码
    • 因为结构化的数据会导致对日志中的密码掩写失败,尽量不要用JSON,xml,yaml来组织密码,而是对每一个敏感的数据创建单独的密码。
  • 所有使用的敏感数据都要注册为密码
    • 注册为密码后,当在日志中出现事,github将会对数据进行掩写。
    • 不光对敏感数据要注册为密码,对数据的变换值比如Base64或者URL-encoded都要进行注册。
  • 审查密码是如何被处理的
    • 审查密码是如何被使用的,你可以通过监视仓库的工作流源码,以确保他们符合预期的方式,而且要检查所有相关的actions。比如,确保密码没有被发往未知的服务器或者被打印在日志里。
    • 监视工作流的日志,检查密码被正确的使用而不是暴露。一般调命令和工具如何输出错误到STDOUT和STDERR中并不是那么明晰,密码可能无意中就被打印到日志中了。所以在测试输入输出时人工的检视工作流日志是一个很好的实践。
  • 最低限度的使用密码
    • 务必确保相关的权限应该设置为最小权限,时刻谨记任何拥有写权限的用户有权限能读取你仓库的所有密码的配置参数。
    • Actions可以用从github.token中获取的GITHUB_TOKEN来加强安全。给GITHUB_TOKEN默认设置当前仓库的只读权限是一个很好的安全实践,这个权限可以在使用中按需来增加。
  • 审查和轮转密码
    • 定期地检视密码以确保其仍然在被使用,及时删除不被使用的密码。
    • 定期轮转密码以减少密码的有效窗口期。
  • 使用审查员机制
    • 可以使用审查员来保护环境变量中的密码。一个工作流不能直接访问密码,直到被审查员授权。

使用CODEOWNERS 来监控变化

你可以使用CODEOWNERS 特性来监控工作流来如何产生变化。比如你的工作流文件存放在.github/workflows,你可以将这个文件夹添加到代码归属列表,任何对这些文件的变化都需要指定的检视员的审批。

了解脚本的注入风险

当创建了一个工作流或者custom actions或者composite actions时,你需要时刻想着你的代码可能需要处理攻击者的非法输入。比如,攻击者可能会添加恶意的命令和脚本在一个内容中,这些字符可能会被解释为代码,从而在你的目标中运行。

攻击者能添加恶意的内容到github context中,这些内容应该被当做非法输入。这些内容经常以body, default_branch, email, head_ref, label, message, name, page_name,ref, and title等关键字结尾。比如github.event.issue.title或者 github.event.pull_request.body。

你需要确保这些值不会直接执行在工作流,actions,API或者会被当做代码解释的任何地方。

另外还有一些不是很明显的潜在恶意输入,像分支名称,邮箱地址等,被灵活的用于内容中。比如zzz";echo${IFS}"hello";#将是一个可能的攻击向量。

脚本工具的例子

- name: Check PR title
        run: |
          title="${{ github.event.pull_request.title }}"
          if [[ $title =~ ^octocat ]]; then
          echo "PR title starts with 'octocat'"
          exit 0
          else
          echo "PR title did not start with 'octocat'"
          exit 1
          fi

此示例容易受到脚本注入的影响,因为 run 命令在运行器上的临时 shell 脚本中执行。 在运行 shell 脚本之前,${{ }} 中的表达式会被评估,然后用结果值替换,这可能使其容易受到 shell 命令注入的攻击。

要将命令注入此工作流,攻击者可以创建一个标题为 a"; ls $GITHUB_WORKSPACE" 的拉取请求:
file
在这个例子中单引号"打断了title="${{ github.event.pull_request.title }}",允许ls命令被执行。你可以在日志中看到ls的输出:
file

减轻脚本注入的最佳实践

以下是几种不同的减轻脚本注入风险的方式:

用action替代内部脚本

这个推荐的方法会创建一个action,输入的内容被当做action的输入参数。这个方法不会被攻击,因为内容不会产生shell脚本行为。

uses: fakeaction/checktitle@v3
with:
    title: ${{ github.event.pull_request.title }}

使用中间环境变量

对于内联脚本,处理不可信输入的首选方法是将表达式的值设置为中间环境变量。

以下示例使用 Bash 将 github.event.pull_request.title 值作为环境变量处理:

- name: Check PR title
        env:
          TITLE: ${{ github.event.pull_request.title }}
        run: |
          if [[ "$TITLE" =~ ^octocat ]]; then
          echo "PR title starts with 'octocat'"
          exit 0
          else
          echo "PR title did not start with 'octocat'"
          exit 1
          fi

这个例子,尝试的脚本注入不会成功:
file
使用这种方法,${{ github.event.issue.title }} 表达式的值存储在内存中并用作变量,并且不与脚本生成过程交互。 此外,考虑使用双引号 shell 变量来避免分词,但这是编写 shell 脚本的许多一般建议之一,并不特定于 GitHub Actions。

使用CodeQL 来分析你的代码

CodeQL Queries
是github 为js仓库提供的一项服务。想使用此服务,你的仓库中要至少有一个js文件。

限制tokens的权限

使用OpenID Connect来获取云端资源

如果您的 GitHub Actions 工作流程需要从支持 OpenID Connect (OIDC) 的云提供商访问资源,您可以将您的工作流程配置为直接向云提供商进行身份验证。 这将让您停止将这些凭据存储为长期机密,并能提供其他安全优势。 有关更多信息,请参阅“关于使用 OpenID Connect 进行安全加固”

使用第三方actions

工作流中的各个作业可以与其他作业交互(和妥协)。 例如,一个作业查询后面的作业使用的环境变量,将文件写入后面的作业处理的共享目录,或者更直接地通过与 Docker 套接字交互并检查其他正在运行的容器并在其中执行命令。

这意味着工作流中单个操作的破坏可能非常重要,因为该破坏的操作将有权访问存储库上配置的所有机密,并且可能能够使用 GITHUB_TOKEN 进行写仓库行为。 因此,从 GitHub 上的第三方仓库执行的action存在重大风险。 有关攻击者可能采取的一些步骤的信息,请参阅“受感染运行程序的潜在影响”。

您可以通过遵循以下良好做法来帮助减轻这种风险:

  • 通过完整的提交SHA来匹配actions
  • 审查action的源码
  • 只有在你信任创建者的情况下才使用tag

重用第三方工作流

要求跟第三方action一样。

密码泄露后,可能遭受的潜影响

一下将介绍攻击者可能通过以下的步骤在Github action中运行恶意命令

获取密码

工作流受puul_request触发,拥有只读权限,无权访问密码。issue_comment, issues 和 push等不同的事件拥有不同的权限,攻击者可能会窃取仓库的密码,从而使用GITHUB_TOKEN的写权限。

  • 如果密码或者token是被写入环境变量,可以使用printenv来显示。
  • 如果密码是明文存在于表达式中,形成的shell脚本将可被访问。
  • 在定制action中,风险取决于怎么使用参数传入的密码:
    uses: fakeaction/publish@v3
    with:
    key: ${{ secrets.PUBLISH_KEY }}

    尽管 GitHub Actions 从内存中清除密码,保证其不在工作流(或包含的操作)中未引用,但坚定的攻击者可以获取 GITHUB_TOKEN 和任何引用的密码。

从运行对象中窃取数据

窃取GITHUB_TOKEN

修改代码仓库数据

仓库交叉访问

当操作不当时,授权一个仓库的GITHUB_TOKEN能访问另一个时将影响github的权限模型。同理,给工作流添加权限时,也必须非常小心。

我们有一个计划去支持仓库交叉访问,但是目前还未实现。当前实现的方法就是将token或者ssh 用作工作流的密码。因为不同的token类型有不同的资源访问权限,用错token类型将产生预期意外的权限。

以下列出了几种在工作流中获取仓库数据的方法,按推荐度降序排列:

  1. GITHUB_TOKEN
    • 这个token在仓库的默认工作流中,有着write-access的权限。job开始时创建,job结束时消亡。
  2. 仓库部署key
  3. github app tokens
  4. Personal access tokens(PAT)
  5. 用户账户下的SSH Keys

自托管环境的加强

github-hosted:隔离的虚拟环境
self-hosted: 自己的vps或者物理系统环境

你应该考虑在子托管环境下:

  • 哪些敏感数据是存在运行目标中的?
  • 目标主机是否有网络权限能访问敏感服务?

一些客户可能会尝试通过实施在每次作业执行后自动销毁自托管运行器的系统来部分减轻这些风险。 但是,这种方法可能没有预期的那么有效,因为无法保证自托管运行器只运行一个作业。 一些作业将使用机密作为命令行参数,在同一运行器上运行的另一个作业可以看到这些参数,例如 ps x -w。 这可能导致秘密泄露。

为自托管环境设计你自己的管理策略

中心化管理

  • 如果每一个小组都管理他们自己的运行环境,那么推荐配置最高的权限。比如,每个小组都有自己的github organization,那么就给他们添加组织权限
  • 你自己是独立的组织

非中心化管理

向您的云提供商进行身份验证

如果您使用 GitHub Actions 部署到云提供商,或打算使用 HashiCorp Vault 进行秘密管理,那么建议您考虑使用 OpenID Connect 为您的工作流运行创建短期、范围广泛的访问令牌。 有关更多信息,请参阅“关于使用 OpenID Connect 进行安全强化”。

审核 GitHub Actions 事件

您可以使用审核日志来监控组织中的管理任务。 审核日志记录操作的类型、运行时间以及执行操作的用户帐户。

例如,您可以使用审计日志来跟踪 org.update_actions_secret 事件,该事件跟踪组织机密的更改:
file

发表评论

您的电子邮箱地址不会被公开。