(この記事は日本語でも読むことが出来ます。)
GitHub is running a bug bounty program on HackerOne, and as part of this program, vulnerability research is permitted by the safe harbor.
This article describes a vulnerability that I discovered as a result of my investigation in compliance with the safe harbor criteria and is not intended to encourage unauthorized vulnerability research activities.
If you find a vulnerability on GitHub, please report it to GitHub Bug Bounty.
In the actions/runner repository, which hosts the source code for the GitHub Actions runner, there was a flaw in the usage of the self-hosted runner, which allowed me to steal the Personal Access Token from GitHub Actions.
Since this token was tied to the GitHub staff account, I could perform various actions as a GitHub staff.
This potentially allowed the insertion of malicious code into repositories such as actions/checkout and actions/cache, which might affect many repositories that use GitHub Actions.
Self-hosted runner, as the name suggests, is a feature that allows users to run GitHub Actions runners on their own machines. It is mainly used in the CI where hardware requirements exist.
This feature is achieved by installing an Actions runner on the user’s machine. And this runner doesn’t isolate the environment for each execution, so the system state is shared among jobs unless the user isolates the environment separately. This behavior is not a security issue if only trusted workflows are executed.
However, there is a workflow trigger in GitHub Actions called pull_request
.
This trigger gets executed when an event related to a pull request occurs. While starting the workflow, it reads the definition file of the workflow from the forked repository1 and runs the workflow in the context of the base repository where the pull request was created, with a read-only token passed.
This means a forked repository can execute arbitrary workflows on GitHub Actions in a public repository. 2
When using a runner hosted by GitHub, the environment is isolated for each workflow execution, so there is no problem with this behavior. However, in the case where the self-hosted runner is used, the environment isn’t isolated for each workflow execution. So, a malicious pull request can execute arbitrary code on the self-hosted runner and compromise the environment.
This allows a malicious pull request to steal sensitive information (e.g., a GitHub access token with write access) later when the compromised runner receives it.
The GitHub documentation explicitly describes this behavior and states that self-hosted runners should not be used in public repositories.
Now, let’s look at the actual workflow that was vulnerable to this attack.
In actions/runner, a workflow for E2E test called e2etest.yml exist.3
This workflow does the following steps:
At first glance, the attack described above seems impossible because the self-hosted runner is removed after running the test.
However, these steps contain a flaw, and executing arbitrary commands on the self-hosted runner was possible during the workflow execution.
In the above steps, remember that the script is executed asynchronously in step 3.
This script sleeps 10 seconds each time after retrieving the list of registered self-hosted runners to avoid getting rate limited.
In other words, there is a maximum delay of a little over 10 seconds from when the self-hosted runner is registered in step 4-2 until the job is executed for the runner in step 3-2.
As the About self-hosted runner
section explains, a self-hosted runner can receive jobs executed from a pull request. By sending a malicious pull request during these 10 seconds, arbitrary commands can be executed on the self-hosted runner, and subsequent steps will be executed in the compromised runner.
Looking at the subsequent steps, we see that the self-hosted runner is removed from the actions/runner repository in steps 4-5.
Typically, the GitHub Token passed to the runner in GitHub Actions doesn’t have permission to register/delete the self-hosted runner, so they used GitHub’s Personal Access Token to register/delete the self-hosted runner, as shown below.
.github/workflows/e2etest.yml line 165 - line 178
- name: Configure Runner
env:
unique_runner_name: linux-x64-${{needs.init.outputs.unique_runner_label}}
run: |
./config.sh --url ${{github.event.repository.html_url}} --unattended --name $unique_runner_name --pat ${{secrets.PAT}} --labels $unique_runner_name --replace
- name: Start Runner and Wait for Job
timeout-minutes: 5
run: |
./run.sh --once
- name: Remove Runner
if: always()
continue-on-error: true
run: |
./config.sh remove --pat ${{secrets.PAT}}
Since the job is executed in the Start Runner and Wait for Job
step, the subsequent Remove Runner
step will be run in the compromised runner.
This means the secrets.PAT
passed to the runner in ./config.sh remove --pat ${{secrets.PAT}}
can be stolen by sending a malicious pull request.
And now, the question is who owns secrets.PAT
.
Fortunately, this token was used in another place for the execution of the workflow, so it was easy to identify the token’s owner.
So, I then checked the user profile and found that the user belongs to @actions and @github.
As mentioned above, this token is used for multiple purposes, so it is assumed to have at least the public_repo
scope.
Therefore, by stealing this token, it is possible to obtain write permission for public repositories of @actions and @github.
This could result in a serious supply chain issue because @actions and @github own many repositories, including the official actions such as actions/checkout and actions/cache.
In this article, I explained how a misconfiguration of GitHub Actions could lead to a serious supply chain risk.
I hope this article helped you understand the importance of security in the CI/CD pipeline.
If you have any questions or comments, please feel free to contact me on Twitter (@ryotkak) or Misskey (@[email protected]).
Date (JST) | Event |
---|---|
2021/06/19 | Vulnerability discovery/reporting |
2021/06/19 | Temporary fix |
2021/06/22 | Permanent fix |
2022/12/18 | Start writing this article |
2023/04/11 | Got permission to publish this article |
2023/04/22 | Published this article |