Summary
Shortly after Hugo Vincent of Synactiv published his blog post on the Dependabot actor confusion technique, I set out to identify interesting repositories vulnerable to the this attack technique. One repository I quickly found was that of the Release Drafter reusable GitHub Action. Anyone with a GitHub account could have used a pull request with the Dependabot actor confusion technique to obtain a GITHUB_TOKEN that could modify the tags associated with the action. This means that ALL downstream users of this action using it via tags (which are mutable!) instead of SHA would be vulnerable to a supply chain attack.
I submitted a report to Google’s VRP showing how this issue could have impacted Google/Accompanist due to their usage of the action by tag, and I also disclosed the vulnerability to the Release Drafter maintainers. Google accepted the report, quickly mitigated the impact on their end, and awarded a generous $7,500 award under their OSS Standard tier for a supply chain compromise.
This was a unique submission because the issue was entirely within the CI/CD configuration of the upstream repository. This writeup should highlight the importance of looking at more than just the codebase itself when assessing the security of an open-source project on GitHub.
Details
In my report, I emphasized that while the vulnerability was within an upstream action, the following characteristics of the Google/accompanist repository made this issue impactful:
- The repository used the release-drafter action by tag.
- The repository used the action within a workflow that had a
GITHUB_TOKEN
with full write permissions. - While I could not perform an overt attack against the upstream, a black hat attacker could. By fixing the above issues (which alone would be considered best practices) Google could protect themselves from the upstream compromise.
The risks associated with using GitHub Actions by tag are widely known and not new. Researchers from Palo Alto Networks presented an excellent talk at DEF CON 31 about how GitHub Actions vulnerabilities can be wormable.
Release-drafter
The release drafter action is one of the most popular third-party actions on GitHub’s Action marketplace. Thousands of repositories use it, and within the context it is used most workflows will have at least write access to the repository, if not other secrets.
In the real world, google/accompanist
might not be a prime target for attackers given how backdooring an action backdoors it for everyone. An NPM Token used in a cryptocurrency company’s workflow definitely would be. Near’s Wallet Selector also used the v5
version of release-drafter: https://github.com/near/wallet-selector/blob/main/.github/workflows/bump-version.yml, and the workflow has access to an NPM Token that an attacker could easily steal.
I have little doubt that a threat actor would have exploited this vulnerability if they had the knowledge and chance to - the NPM token for a wallet selector library is too valuable of a target. Thankfully, the maintainers of release-drafter/release-drafter
deployed a fix on November 1st after I sent a reminder email asking if they had looked into the issue I called out a few weeks prior.
Preparing a Proof of Concept
For this report, I could not conduct an attack against the upstream as that is not owned by Google. Instead, I created a complete mirror of both the Google/accompanist repository and the release-drafter/release-drafter repository. The mirror environment allowed me to simulate the full attack chain starting from the Dependabot confusion Pwn Request all the way to pushing a malicious tag with an exfiltration payload targeting the downstream repository.
The diagram below shows how I configured my mirror environment to exercise the vulnerability path.
Dependabot Confusion
The issue with Release Drafter was that the Dependabot post-update
workflow ran on pull_request_target
, checked out the code from the pull request head, and then used actions/setup-node
with cache: yarn
. This meant that the package.json
file was a trivial code injection point. All of this would be a trivial Pwn Request, which is a widely known vulnerability class, but there was one limitation.
If you look carefully, you’ll see if: ${{ github.actor == 'dependabot[bot]' }}
. Normally, this would not be exploitable, but Hugo Vincent’s blog post released a clever technique that allows sending a synchronize
event associated with the Dependabot actor. This meant anyone could have exploited this workflow to obtain GITHUB_TOKEN
with write access to the release-drafter/release-drafter
repository. I won’t walk through the technique in detail as Hugo’s post explains it will, but it simply required the following steps:
- Fork the target repository
- Enable Dependabot on the fork
- Add the payload to the default branch of the fork (the
main
branch in most cases) - Create a fork PR from the feature branch created by Dependabot in the fork to the target repository
- Comment
@dependabot recreate
in the PR Dependabot created within the fork. - Dependabot will update the PR head with the new changes from main, but because that branch is also the head branch for a second fork PR, it will trigger a synchronize event in the base repository.
In my mirror of the release-drafter repository, I used a GITHUB_TOKEN
exfiltration payload followed by a sleep for 30 minutes. Note the workflow run time and that the synchronize event came from dependabot. My “attacker” account in this case was named “derpendabot-bot”.
Crafting the Payload
After obtaining the GITHUB_TOKEN
I needed to backdoor the v5
tag because the google/accompanist
repository used the action by the v5
tag.
In my fork, I simply committed a change to the action.yml
to make it a composite action and run an exfiltration payload.
Then, I force updated the v5
tag to point to the new commit and pushed it to the repository.
A motivated attacker could go even further with an action modification payload. As a first step, they would place the payload as obfuscated Javascript within the dist file. For most Actions, this file is several MB, and the commit for major version tags are designed to change, so a commit reference change would not be unusual. Here is release-drafter’s v6.0.0
file.
In order to avoid attacking repositories that are not their true target, an attacker could encrypt the actual payload with a key that would only be present within a target repository’s workflow.
Downstream Usage
Now, let’s look at the Update release
workflow within the google/accompanist
repository. The workflow ran on every push to main
and used the v5
version of the action.
Close observation of previous runs also revealed that the workflow had a GITHUB_TOKEN
with full write access. This meant that an attacker could use it to modify code, releases, and potentially approve and merge their own pull requests.
In my mirror environment I pushed a change to the main
branch of the repository. This triggered the Update release
workflow. Instead of running the expected release-drafter action, it ran my simple curl | bash
payload.
Conclusion
This scenario emphasizes the importance of using third party GitHub Actions by SHA instead of mutable tags. Otherwise, the third party’s attack surface is your attack surface as well. If a new vulnerability or technique allows an attacker to compromise the release process of an upstream Action, then your repository is immediately vulnerable. It is not just vulnerabilities - attackers frequently use spear-phishing to target developers to conduct supply chain attacks ( just look at the recent Lottie Player incident, where the attacker likely made off with over $700k in stolen cryptocurrency from a single victim!). It would not be surprising if attackers targeted developers of popular reusable actions as well.
References
- GitHub Actions Exploitation: Dependabot - https://www.synacktiv.com/publications/github-actions-exploitation-dependabot
- The GitHub Actions Worm - Palo Alto Networks -https://www.paloaltonetworks.com/blog/prisma-cloud/github-actions-worm-dependencies/
- https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/