Software/Scripts Four tips to keep your GitHub Actions workflows secure

Git

Premium
Premium
Регистрация
09.02.2010
Сообщения
270
Реакции
41
Баллы
28
Native language | Родной язык
English
Continuous Integration and Continuous Deployment (CI/CD) software supply chains are a lucrative target for threat actors. GitHub Actions is one of the most widely used platforms for automation, making it an important target.

For the past few months, the GitHub Security Lab has been collaborating with a team of researchers from Purdue University ( , ) and North Carolina State University ( ). Their research, which they are presenting this week at the , is about how they found a number of code injection vulnerabilities in GitHub Actions workflows among open source projects hosted on GitHub. We confirmed their findings by verifying a random sample of the vulnerabilities found, and also advised them on how to report the vulnerabilities to the large number of affected projects.

The injection vulnerabilities the researchers found are all variations of the same patterns we’ve described in , which we share at the end of this blog post. Please read on for the most important points from those posts and use these tips to keep your workflows secure.

Understanding command injection vulnerabilities in GitHub Actions workflows​


A is a program that starts automatically when a specific repository event occurs. As with any program potentially started by an external user, user-controlled inputs should be treated as untrusted. In the context of workflows, this means values such as github.event.issue.title or github.event.issue.body. In particular, GitHub Actions is a powerful language-independent feature which may lead to script injections when used in such blocks as run.

This is a simple example of a workflow that is vulnerable to command injection:

Код:
- name: print title
  run: echo "${{ github.event.issue.title }}"

To exploit this, an attacker would create an issue with a title like $(touch pwned.txt):

Screenshot of a new GitHub Issue named $(touch pwned.txt) being created.


Due to the way that the ${{}} syntax gets expanded, this causes the workflow to execute the following command:

Код:
echo "$(touch pwned.txt)"

Since expression evaluation is language independent, the injection type is not Bash shell-specific. For example, when ${{}} is used in a JS script, a syntactically valid construct can be used for injection there, too.

An attacker could use an injection vulnerability like this to achieve something far more malicious than just modifying a local file. For example, they could upload secrets to a website that they control, or commit new code to your repository to add a backdoor vulnerability or supply chain attack.

1: Don’t use ${{ }} syntax in the run section to avoid unexpected substitution behavior​


Our recommendation is to use intermediate environment variables for the potentially untrusted input and then use language-specific capabilities to retrieve the value of the variable:

Код:
- name: print title
  env:
    TITLE: ${{ github.event.issue.title }}
  run: echo "$TITLE"

or

Код:
- name: print title
  env:
    TITLE: ${{ github.event.issue.title }}
  uses: actions/github-script@v6
  with:
    script: console.log(process.env.TITLE)

When used in the run or script section, the ${{ }} syntax is almost always very dangerous. It gets macro-expanded to arbitrary text in the body of the command, which means that it’s usually very easy to exploit. Using the ${{ }} syntax in the env section is safer because the user-controlled string is stored in a variable instead. The syntax "$TITLE" in the updated run section is standard Bash syntax, which is widely known and therefore less likely to behave in a way that the author of the workflow didn’t expect. Similarly, the syntax process.env.TITLE is standard JavaScript syntax, so the code will behave as expected for a JavaScript program. Please note that this solution doesn’t prevent other types of vulnerabilities: the TITLE environment still contains untrusted input and still needs to be handled with care.

2: Enable code scanning for workflows​


The recommendation above is a safer way to use the${{ }} syntax, but it doesn’t prevent all command injection vulnerabilities. Therefore, we recommend carefully reviewing your workflows, focusing on the usage of untrusted input. Additionally, to prevent accidentally introducing similar vulnerabilities in new code, we recommend enabling for the repository. The Security Lab, in collaboration with the code scanning team, has written queries that can catch unsafe interpolation usage with untrusted input.

The CodeQL workflow scanning queries are (currently) only included in the query suite for JavaScript, so they’re only enabled by default if your project is written in JavaScript. If the main programming language of your project is something else, such as Python or Java, then you need to manually modify the CodeQL workflow to add JavaScript as an additional language. of how to do this. The scanning will work even if your repository doesn’t contain any JavaScript; and if you are interested only in workflows, but not other JavaScript files, you can .

3: Use the principle of least privilege​


The impact of an injection vulnerability may be greatly reduced by using the principle of least privilege. Every GitHub workflow receives a temporary repository access token (GITHUB_TOKEN). These tokens originally had a very broad set of permissions with full read and write access to the repository (except for pull requests from forks). In 2021, GitHub introduced for workflow tokens and, today, . However, a significant number of workflows still use a write-all token due to grandfathered default workflow permission settings in older repositories. If you want to check if you are using a broad default permission for your workflow tokens, you can go to the repository (or organization) settings→actions and check the “Workflow permissions” section:

Screenshot of the Workflow permissions section of a repository's settings. The radio button for Read and write permissions is selected.


Recently, the Security Lab , which we recommend you use to make the transition smoother.

An important security guarantee that GitHub makes is that workflows triggered on pull_request from forks are run with minimal privileges: no access to secrets and the repository token is read-only. Thus, if a workflow contains potential injection vulnerabilities, but is triggered only on pull_request, then the impact is minimal, because only pull requests from users able to create branches in the same repository are able to trigger the workflow with higher privileges. However, the researchers found a separate vulnerability which enabled external users to trigger a privileged workflow run by creating a pull request between already existing branches in the same repository. The exploitability was limited to the cases when the target branch workflow contained an injection from a pull request input such as github.event.pull_request.title or github.event.pull_request.body. The researchers reported the issue (responsibly) to GitHub and it has since been fixed. Nevertheless, it is better to keep workflows injection free, even if they run with minimal privileges.

4: Enable Private Vulnerability Reporting (PVR)​


Finding an appropriate contact to disclose a potential vulnerability to is often an unnecessarily complicated task for security researchers, and is why we introduced last year, which provides a direct collaboration channel for security researchers and maintainers where they can securely discuss the issue and work on mitigations in a private pull request. We encourage all maintainers to .

Conclusion​


We would like to thank the research team from Purdue University and North Carolina State University for doing this work and for helping to protect GitHub’s users. Open source security is a collaborative effort and this research project is a great example of how it should be done.

For more advice on how to keep your workflows secure, check out these blog posts:



Notes​


  1. When you , the language of your project is automatically detected. The suite of CodeQL queries is different for each language.

The post appeared first on .
 
198 111Темы
635 082Сообщения
3 618 399Пользователи
DimJenНовый пользователь
Верх