New Descriptions

This commit is contained in:
Alvaro Muñoz
2024-08-09 17:06:06 +02:00
parent d8df3ff6b3
commit 9411fac4d0
25 changed files with 1860 additions and 8 deletions

View File

@@ -0,0 +1,37 @@
# Environment Path Injection
## Description
GitHub Actions allows to define the system PATH variable by writing to a file pointed to by the `GITHUB_PATH` environment variable. Writing to this file will prepend a directory to the system PATH variable and automatically makes it available to all subsequent actions in the current job. E.g.
```bash
echo "$HOME/.local/bin" >> $GITHUB_PATH
```
If an attacker can control the contents of the path being assigned to the system PATH, they will be able to influence what commands are run in subsequen steps of the same job.
## Recommendations
- Do Not Allow Untrusted Data to Influence The System PATH: Avoid using untrusted data sources (e.g., artifact content) to define the system PATH.
## Examples
### Incorrect Usage
Consider the following basic setup where an environment variable `MYVAR` is set and used in different steps:
```yaml
steps:
- name: Set the path
env:
BODY: ${{ github.event.comment.body }}
run: |
PATH=$(echo "$BODY" | grep -oP 'system path: \K\S+')
echo "$PATH" >> "$GITHUB_PATH"
```
If an attacker can manipulate the value being set, such as through artifact downloads or user inputs, they can potentially change the system PATH and get arbitrary command execution in subsequent steps.
## References
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)

View File

@@ -0,0 +1,37 @@
# Environment Path Injection
## Description
GitHub Actions allows to define the system PATH variable by writing to a file pointed to by the `GITHUB_PATH` environment variable. Writing to this file will prepend a directory to the system PATH variable and automatically makes it available to all subsequent actions in the current job. E.g.
```bash
echo "$HOME/.local/bin" >> $GITHUB_PATH
```
If an attacker can control the contents of the path being assigned to the system PATH, they will be able to influence what commands are run in subsequen steps of the same job.
## Recommendations
- Do Not Allow Untrusted Data to Influence The System PATH: Avoid using untrusted data sources (e.g., artifact content) to define the system PATH.
## Examples
### Incorrect Usage
Consider the following basic setup where an environment variable `MYVAR` is set and used in different steps:
```yaml
steps:
- name: Set the path
env:
BODY: ${{ github.event.comment.body }}
run: |
PATH=$(echo "$BODY" | grep -oP 'system path: \K\S+')
echo "$PATH" >> "$GITHUB_PATH"
```
If an attacker can manipulate the value being set, such as through artifact downloads or user inputs, they can potentially change the system PATH and get arbitrary command execution in subsequent steps.
## References
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)

View File

@@ -0,0 +1,117 @@
# Environment Variable Injection
## Description
GitHub Actions allows to define Environment Variables by writing to a file pointed to by the `GITHUB_ENV` environment variable:
This file should lines in the `KEY=VALUE` format:
```bash
steps:
- name: Set the value
id: step_one
run: |
echo "action_state=yellow" >> "$GITHUB_ENV"
```
It is also possible to define a multiline variables by using the following format:
```
KEY<<{delimiter}
VALUE
VALUE
{delimiter}
```
```bash
steps:
- name: Set the value in bash
id: step_one
run: |
{
echo 'JSON_RESPONSE<<EOF'
curl https://example.com
echo EOF
} >> "$GITHUB_ENV"
```
If an attacker can control the contents of the values assigned to these variables and these are not properly sanitized, they will be able to inject additional variables by injecting new lines or `{delimiters}`.
## Recommendations
1. **Do Not Allow Untrusted Data to Influence Environment Variables**:
- Avoid using untrusted data sources (e.g., artifact content) to define environment variables.
- Validate and sanitize all inputs before using them in environment settings.
2. **Do Not Allow New Lines When Defining Single Line Environment Variables**:
- `echo "BODY=$(echo "$BODY" | tr -d '\n')" >> "$GITHUB_ENV"`
3. **Use Unique Identifiers When Defining Multi Line Environment Variables**:
```bash
steps:
- name: Set the value in bash
id: step_one
run: |
# Generate a UUID
UUID=$(uuidgen)
{
echo "JSON_RESPONSE<<EOF$UUID"
curl https://example.com
echo "EOF$UUID"
} >> "$GITHUB_ENV"
```
## Examples
### Example of Vulnerability
Consider the following basic setup where an environment variable `MYVAR` is set and used in different steps:
```yaml
steps:
- name: Set the value
id: step_one
env:
BODY: ${{ github.event.comment.body }}
run: |
REPLACED=$(echo "$BODY" | sed 's/FOO/BAR/g')
echo "BODY=$REPLACED" >> "$GITHUB_ENV"
```
If an attacker can manipulate the value being set, such as through artifact downloads or user inputs, they can potentially inject new Environment variables. For example, they could write an Issue comment like:
```
FOO
NEW_ENV_VAR=MALICIOUS_VALUE
```
Likewise, if the attacker controls a file in the Runner's workspace (eg: the workflow checkouts untrusted code or downloads an untrusted artifact), and the contents of that file are assigned to an environment variable such as:
```bash
- run: |
PR_NUMBER=$(cat pr-number.txt)
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
```
An attacker could craft a malicious artifact that writes dangerous environment variables:
```bash
- run: |
echo -e "666\nNEW_ENV_VAR=MALICIOUS_VALUE" > pr-number.txt
- uses: actions/upload-artifact@v4
with:
name: pr-number
path: ./pr-number.txt
```
### Exploitation
An attacker will be able to run arbitrary code by injecting environment variables such as `LD_PRELOAD`, `BASH_ENV`, etc.
## References
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)
- [GitHub Actions Exploitation: Repo Jacking and Environment Manipulation](https://www.synacktiv.com/publications/github-actions-exploitation-repo-jacking-and-environment-manipulation)

View File

@@ -0,0 +1,117 @@
# Environment Variable Injection
## Description
GitHub Actions allows to define Environment Variables by writing to a file pointed to by the `GITHUB_ENV` environment variable:
This file should lines in the `KEY=VALUE` format:
```bash
steps:
- name: Set the value
id: step_one
run: |
echo "action_state=yellow" >> "$GITHUB_ENV"
```
It is also possible to define a multiline variables by using the following format:
```
KEY<<{delimiter}
VALUE
VALUE
{delimiter}
```
```bash
steps:
- name: Set the value in bash
id: step_one
run: |
{
echo 'JSON_RESPONSE<<EOF'
curl https://example.com
echo EOF
} >> "$GITHUB_ENV"
```
If an attacker can control the contents of the values assigned to these variables and these are not properly sanitized, they will be able to inject additional variables by injecting new lines or `{delimiters}`.
## Recommendations
1. **Do Not Allow Untrusted Data to Influence Environment Variables**:
- Avoid using untrusted data sources (e.g., artifact content) to define environment variables.
- Validate and sanitize all inputs before using them in environment settings.
2. **Do Not Allow New Lines When Defining Single Line Environment Variables**:
- `echo "BODY=$(echo "$BODY" | tr -d '\n')" >> "$GITHUB_ENV"`
3. **Use Unique Identifiers When Defining Multi Line Environment Variables**:
```bash
steps:
- name: Set the value in bash
id: step_one
run: |
# Generate a UUID
UUID=$(uuidgen)
{
echo "JSON_RESPONSE<<EOF$UUID"
curl https://example.com
echo "EOF$UUID"
} >> "$GITHUB_ENV"
```
## Examples
### Example of Vulnerability
Consider the following basic setup where an environment variable `MYVAR` is set and used in different steps:
```yaml
steps:
- name: Set the value
id: step_one
env:
BODY: ${{ github.event.comment.body }}
run: |
REPLACED=$(echo "$BODY" | sed 's/FOO/BAR/g')
echo "BODY=$REPLACED" >> "$GITHUB_ENV"
```
If an attacker can manipulate the value being set, such as through artifact downloads or user inputs, they can potentially inject new Environment variables. For example, they could write an Issue comment like:
```
FOO
NEW_ENV_VAR=MALICIOUS_VALUE
```
Likewise, if the attacker controls a file in the Runner's workspace (eg: the workflow checkouts untrusted code or downloads an untrusted artifact), and the contents of that file are assigned to an environment variable such as:
```bash
- run: |
PR_NUMBER=$(cat pr-number.txt)
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
```
An attacker could craft a malicious artifact that writes dangerous environment variables:
```bash
- run: |
echo -e "666\nNEW_ENV_VAR=MALICIOUS_VALUE" > pr-number.txt
- uses: actions/upload-artifact@v4
with:
name: pr-number
path: ./pr-number.txt
```
### Exploitation
An attacker will be able to run arbitrary code by injecting environment variables such as `LD_PRELOAD`, `BASH_ENV`, etc.
## References
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)
- [GitHub Actions Exploitation: Repo Jacking and Environment Manipulation](https://www.synacktiv.com/publications/github-actions-exploitation-repo-jacking-and-environment-manipulation)

View File

@@ -0,0 +1,41 @@
# Argument Injection in GitHub Actions
## Description
Passing user-controlled arguments to certain commands in the context of `Run` steps may lead to arbitrary code execution.
Argument injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token might have write access to the repository, allowing an attacker to use the token to make changes to the repository.
## Recommendations
When possible avoid passing user-controlled data to commands which may spawn new processes using some of their arguments.
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
## Examples
### Incorrect Usage
The following example lets a user inject an arbitrary shell command through argument injection:
```yaml
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- env:
BODY: ${{ github.event.comment.body }}
run: |
cat file.txt | sed "s/BODY_PLACEHOLDER/$BODY/g" > replaced.txt
```
An attacker may set the body of an Issue comment to `BAR|g;1e whoami;#` and the command `whoami` will get executed during the `sed` operation.
## References
- [Common Weakness Enumeration: CWE-88](https://cwe.mitre.org/data/definitions/88.html).
- [Argument Injection Explained](https://sonarsource.github.io/argument-injection-vectors/explained/)
- [Argument Injection Vectors](https://sonarsource.github.io/argument-injection-vectors/)
- [GTFOBins](https://gtfobins.github.io/)

View File

@@ -0,0 +1,41 @@
# Argument Injection in GitHub Actions
## Description
Passing user-controlled arguments to certain commands in the context of `Run` steps may lead to arbitrary code execution.
Argument injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token might have write access to the repository, allowing an attacker to use the token to make changes to the repository.
## Recommendations
When possible avoid passing user-controlled data to commands which may spawn new processes using some of their arguments.
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
## Examples
### Incorrect Usage
The following example lets a user inject an arbitrary shell command through argument injection:
```yaml
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- env:
BODY: ${{ github.event.comment.body }}
run: |
cat file.txt | sed "s/BODY_PLACEHOLDER/$BODY/g" > replaced.txt
```
An attacker may set the body of an Issue comment to `BAR|g;1e whoami;#` and the command `whoami` will get executed during the `sed` operation.
## References
- [Common Weakness Enumeration: CWE-88](https://cwe.mitre.org/data/definitions/88.html).
- [Argument Injection Explained](https://sonarsource.github.io/argument-injection-vectors/explained/)
- [Argument Injection Vectors](https://sonarsource.github.io/argument-injection-vectors/)
- [GTFOBins](https://gtfobins.github.io/)

View File

@@ -1,16 +1,20 @@
# Code Injection in GitHub Actions
## Description
Using user-controlled input in GitHub Actions may lead to code injection in contexts like _run:_ or _script:_.
Code injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token might have write access to the repository, allowing an attacker to use the token to make changes to the repository.
## Recommendation
## Recommendations
The best practice to avoid code injection vulnerabilities in GitHub workflows is to set the untrusted input value of the expression to an intermediate environment variable and then use the environment variable using the native syntax of the shell/script interpreter (that is, not _${{ env.VAR }}_).
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
## Example
## Examples
### Incorrect Usage
The following example lets a user inject an arbitrary shell command:
@@ -40,6 +44,8 @@ jobs:
echo '${{ env.BODY }}'
```
### Correct Usage
The following example uses shell syntax to read the environment variable and will prevent the attack:
```yaml
@@ -53,6 +59,22 @@ jobs:
echo "$BODY"
```
The following example uses `process.env` to read environment variables within JavaScript code.
```yaml
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- uses: uses: actions/github-script@v4
env:
BODY: ${{ github.event.issue.body }}
with:
script: |
const { BODY } = process.env
...
```
## References
- GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure: Untrusted input](https://securitylab.github.com/research/github-actions-untrusted-input).

View File

@@ -1,16 +1,20 @@
# Code Injection in GitHub Actions
## Description
Using user-controlled input in GitHub Actions may lead to code injection in contexts like _run:_ or _script:_.
Code injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token might have write access to the repository, allowing an attacker to use the token to make changes to the repository.
## Recommendation
## Recommendations
The best practice to avoid code injection vulnerabilities in GitHub workflows is to set the untrusted input value of the expression to an intermediate environment variable and then use the environment variable using the native syntax of the shell/script interpreter (that is, not _${{ env.VAR }}_).
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
## Example
## Examples
### Incorrect Usage
The following example lets a user inject an arbitrary shell command:
@@ -40,6 +44,8 @@ jobs:
echo '${{ env.BODY }}'
```
### Correct Usage
The following example uses shell syntax to read the environment variable and will prevent the attack:
```yaml
@@ -53,6 +59,22 @@ jobs:
echo "$BODY"
```
The following example uses `process.env` to read environment variables within JavaScript code.
```yaml
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- uses: uses: actions/github-script@v4
env:
BODY: ${{ github.event.issue.body }}
with:
script: |
const { BODY } = process.env
...
```
## References
- GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure: Untrusted input](https://securitylab.github.com/research/github-actions-untrusted-input).

View File

@@ -0,0 +1,13 @@
# Use of Actions with known vulnerabilities
## Description
The security of the workflow and the repository could be compromised by GitHub Actions workflows that utilize third-party GitHub Actions with known vulnerabilities.
## Recommendations
Either remove the component from the workflow or upgrade it to a version that is not vulnerable.
## References
- [GitHub Docs: Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot)

View File

@@ -1,9 +1,11 @@
# Actions Job and Workflow Permissions are not set
A GitHub Actions job or workflow hasn't set permissions to restrict privileges to the workflow job.
A workflow job by default without the `permissions` key or a root workflow `permissions` will run with all the permissions which can be given to a workflow.
## Description
## Recommendation
A GitHub Actions job or workflow hasn't set explicit permissions to restrict privileges to the workflow job.
A workflow job by default without the `permissions` key or a root workflow `permissions` will run with the default permissions defined at the repository level. For organizations created before February 2023, including many significant OSS projects and corporations, the default permissions grant read-write access to repositories, and new repositories inherit these old, insecure permissions.
## Recommendations
Add the `permissions` key to the job or workflow (applied to all jobs) and set the permissions to the least privilege required to complete the task:
@@ -12,11 +14,18 @@ name: "My workflow"
permissions:
contents: read
pull-requests: write
```
# or
or
```yaml
jobs:
my-job:
permissions:
contents: read
pull-requests: write
```
## References
- [Assigning permissions to jobs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/assigning-permissions-to-jobs)

View File

@@ -0,0 +1,57 @@
# Improper Access Control
## Description
An authorization check may not be properly implemented, allowing an attacker to mutate the code after it has been reviewed.
## Recommendations
When using Label gates, make sure that the code cannot be modified after it has been reviewed and the label has been set.
## Examples
### Incorrect Usage
The following example shows a job that requires the label `safe to test` to be set before running untrusted code. However, the workflow gets triggered on `synchronize` activity type and, therefore, it will get triggered every time there is a change in the Pull Request. An attacker can modify the code of the Pull Request after the code has been reviewed and the label has been set.
```yaml
on:
pull_request_target:
types: [opened, synchronize]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repo for OWNER TEST
uses: actions/checkout@v3
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
with:
ref: ${{ github.event.pull_request.head.ref }}
- run: ./cmd
```
### Correct Usage
Make sure that the workflow only gets triggered when the label is set and use an inmutable commit (`github.event.pull_request.head.sha`) instead of a mutable reference.
```yaml
on:
pull_request_target:
types: [labeled]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repo for OWNER TEST
uses: actions/checkout@v3
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
with:
ref: ${{ github.event.pull_request.head.sha}}
- run: ./cmd
```
## References
- [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target)

View File

@@ -0,0 +1,52 @@
# Excessive Secrets Exposure
## Description
When the workflow runner cannot determine what secrets are needed to run the workflow, it will pass all the available secrets to the runner including organization and repository secrets. This violates the least privileged principle and increases the impact of a potential vulnerability affecting the workflow.
## Recommendations
Only pass those secrets that are needed by the workflow. Avoid using expressions such as `toJSON(secrets)` or dynamically accessed secrets such as `secrets[format('GH_PAT_%s', matrix.env)]` since the workflow will need to receive all secrets to decide at runtime which one needs to be used.
## Examples
### Incorrect Usage
```yaml
env:
ALL_SECRETS: ${{ toJSON(secrets) }}
```
```yaml
strategy:
matrix:
env: [PROD, DEV]
env:
GH_TOKEN: ${{ secrets[format('GH_PAT_%s', matrix.env)] }}
```
### Correct Usage
```yaml
env:
NEEDED_SECRET: ${{ secrets.GH_PAT }}
```
```yaml
strategy:
matrix:
env: [PROD, DEV]
---
if: matrix.env == "PROD"
env:
GH_TOKEN: ${{ secrets.GH_PAT_PROD }}
---
if: matrix.env == "DEV"
env:
GH_TOKEN: ${{ secrets.GH_PAT_DEV }}
```
## References
- [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-encrypted-secrets-in-a-workflow)
- [Job uses all secrets](https://github.com/boostsecurityio/poutine/blob/main/docs/content/en/rules/job_all_secrets.md)

View File

@@ -0,0 +1,37 @@
# Unmasked Secret Exposure
## Description
Secrets derived from other secrets are not know to the workflow runner and therefore not masked unless explicitly registered.
## Recommendations
Avoid defining non-plain secrets. For example, do not define a new secret containing a JSON object and then read properties out of it from the workflow since these read values will not be masked by the workflow runner.
## Examples
### Incorrect Usage
```yaml
- env:
username: ${{ fromJson(secrets.AZURE_CREDENTIALS).clientId }}
password: ${{ fromJson(secrets.AZURE_CREDENTIALS).clientSecret }}
run: |
echo "$username"
echo "$password"
```
### Correct Usage
```yaml
- env:
username: ${{ secrets.AZURE_CREDENTIALS_CLIENT_ID }}
password: ${{ secrets.AZURE_CREDENTIALS_CLIENT_SECRET }}
run: |
echo "$username"
echo "$password"
```
## References
- [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-encrypted-secrets-in-a-workflow)

View File

@@ -0,0 +1,83 @@
# Cache Poisoning in GitHub Actions
## Description
GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache, potentially leading to code execution in privileged workflows.
An attacker with the ability to run code in the context of the default branch (e.g. through Code Injection or Execution of Untrusted Code) can exploit this to:
1. Steal the cache access token and URL
2. Fill the cache to trigger eviction of legitimate entries
3. Poison cache entries with malicious payloads
4. Achieve code execution in privileged workflows that restore the poisoned cache
This allows lateral movement from low-privileged to high-privileged workflows within a repository.
### Cache Structure
In GitHub Actions, cache scopes are primarily determined by the branch structure. Branches are considered the main security boundary for GitHub Actions caching. This means that cache entries are generally scoped to specific branches.
- **Access to Parent Branch Caches**: Feature branches (or child branches) created off of a parent branch (like `main` or `dev`) can access caches from the parent branch. For instance, a feature branch off of `main` will be able to access the cache from `main`.
- **Sibling Branches**: Sibling branches, meaning branches that are created from the same parent but not from each other, do not share caches. For example, two branches created off of `main` will not be able to access each others caches directly.
Due to the above design, if something is cached in the context of the default branch (e.g., `main`), it becomes accessible to any feature branch derived from `main`.
## Recommendations
1. Avoid using caching in workflows that handle sensitive operations like releases.
2. If caching must be used:
- Validate restored cache contents before use
- Use short-lived, workflow-specific cache keys
- Clear caches regularly
3. Implement strict isolation between untrusted and privileged workflow execution:
4. Never run untrusted code in the context of the default branch
5. Sign the cache value cryptographically and verify the signature before usage.
## Examples
### Incorrect Usage
The following workflow is vulnerable to code injection in a non-privileged job but in the context of the default branch.
```yaml
name: Vulnerable Workflow
on:
issue_comment:
types: [created]
jobs:
pr-comment:
permissions: {}
runs-on: ubuntu-latest
steps:
- run: |
echo ${{ github.event.comment.body }}
```
### Correct Usage
The following workflow is not vulnerable to code injections even if it runs in the context of the default branch.
```yaml
name: Secure Workflow
on:
issue_comment:
types: [created]
jobs:
pr-comment:
permissions: {}
runs-on: ubuntu-latest
steps:
- env:
BODY: ${{ github.event.comment.body }}
run: |
echo "$BODY"
```
## References
- [The Monsters in Your Build Cache GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
- [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
- [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/)

View File

@@ -0,0 +1,101 @@
# Cache Poisoning in GitHub Actions
## Description
GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache, potentially leading to code execution in privileged workflows.
An attacker with the ability to run code in the context of the default branch (e.g. through Code Injection or Execution of Untrusted Code) can exploit this to:
1. Steal the cache access token and URL
2. Fill the cache to trigger eviction of legitimate entries
3. Poison cache entries with malicious payloads
4. Achieve code execution in privileged workflows that restore the poisoned cache
This allows lateral movement from low-privileged to high-privileged workflows within a repository.
### Cache Structure
In GitHub Actions, cache scopes are primarily determined by the branch structure. Branches are considered the main security boundary for GitHub Actions caching. This means that cache entries are generally scoped to specific branches.
- **Access to Parent Branch Caches**: Feature branches (or child branches) created off of a parent branch (like `main` or `dev`) can access caches from the parent branch. For instance, a feature branch off of `main` will be able to access the cache from `main`.
- **Sibling Branches**: Sibling branches, meaning branches that are created from the same parent but not from each other, do not share caches. For example, two branches created off of `main` will not be able to access each others caches directly.
Due to the above design, if something is cached in the context of the default branch (e.g., `main`), it becomes accessible to any feature branch derived from `main`.
## Recommendations
1. Avoid using caching in workflows that handle sensitive operations like releases.
2. If caching must be used:
- Validate restored cache contents before use
- Use short-lived, workflow-specific cache keys
- Clear caches regularly
3. Implement strict isolation between untrusted and privileged workflow execution:
4. Never run untrusted code in the context of the default branch
5. Sign the cache value cryptographically and verify the signature before usage.
## Examples
### Incorrect Usage
The following workflow is caching an attacker-controlled file (`large_file`) in the context of the default branch.
```yaml
name: Vulnerable Workflow
on:
issue_comment:
types: [created]
jobs:
pr-comment:
permissions: read-all
runs-on: ubuntu-latest
steps:
- uses: xt0rted/pull-request-comment-branch@v2
id: comment-branch
- uses: actions/checkout@v3
with:
ref: ${{ steps.comment-branch.outputs.head_sha }}
- name: Set up Python 3.10
uses: actions/setup-python@v5
- name: Cache pip dependencies
uses: actions/cache@v4
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: ${{ runner.os }}-pip-
```
### Correct Usage
The following workflow is not checking out untrusted files and, therefore, is caching trusted files only.
```yaml
name: Secure Workflow
on:
issue_comment:
types: [created]
jobs:
pr-comment:
permissions: read-all
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v5
- name: Cache pip dependencies
uses: actions/cache@v4
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: ${{ runner.os }}-pip-
```
## References
- [The Monsters in Your Build Cache GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
- [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
- [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/)

View File

@@ -0,0 +1,85 @@
# Cache Poisoning in GitHub Actions
## Description
GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache, potentially leading to code execution in privileged workflows.
An attacker with the ability to run code in the context of the default branch (e.g. through Code Injection or Execution of Untrusted Code) can exploit this to:
1. Steal the cache access token and URL
2. Fill the cache to trigger eviction of legitimate entries
3. Poison cache entries with malicious payloads
4. Achieve code execution in privileged workflows that restore the poisoned cache
This allows lateral movement from low-privileged to high-privileged workflows within a repository.
### Cache Structure
In GitHub Actions, cache scopes are primarily determined by the branch structure. Branches are considered the main security boundary for GitHub Actions caching. This means that cache entries are generally scoped to specific branches.
- **Access to Parent Branch Caches**: Feature branches (or child branches) created off of a parent branch (like `main` or `dev`) can access caches from the parent branch. For instance, a feature branch off of `main` will be able to access the cache from `main`.
- **Sibling Branches**: Sibling branches, meaning branches that are created from the same parent but not from each other, do not share caches. For example, two branches created off of `main` will not be able to access each others caches directly.
Due to the above design, if something is cached in the context of the default branch (e.g., `main`), it becomes accessible to any feature branch derived from `main`.
## Recommendations
1. Avoid using caching in workflows that handle sensitive operations like releases.
2. If caching must be used:
- Validate restored cache contents before use
- Use short-lived, workflow-specific cache keys
- Clear caches regularly
3. Implement strict isolation between untrusted and privileged workflow execution:
4. Never run untrusted code in the context of the default branch
5. Sign the cache value cryptographically and verify the signature before usage.
## Examples
### Incorrect Usage
The following workflow runs untrusted code in a non-privileged job but in the context of the default branch.
```yaml
name: Vulnerable Workflow
on:
pull_request_target:
branches: [main]
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run tests
run: ./run_tests.sh
```
### Correct Usage
The following workflow runs untrusted code in a non-privileged job and in the context of a non-default branch.
```yaml
name: Secure Workflow
on:
pull_request:
branches: [main]
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run tests
run: ./run_tests.sh
```
## References
- [The Monsters in Your Build Cache GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
- [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
- [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/)

View File

@@ -0,0 +1,168 @@
# Untrusted Checkout TOCTOU
## Description
Untrusted Checkout is protected by a security check but the checked-out branch can be changed after the check.
## Recommendations
Verify that the code has not been modified after the security check. This may be achieved differently depending on the type of check:
- Issue Ops: Verify that Commit containing the code to be executed was commited **before** then date the of the comment.
- Deployment Environment Approval: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
- Label Gates: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
## Examples
### Incorrect Usage (Issue Ops)
The following workflow runs untrusted code after either a member or admin of the repository comments on a Pull Request with the text `/run-tests`. Although it may seem secure, the workflow is checking out a mutable reference (`${{ steps.comment-branch.outputs.head_ref }}`) and therefore the code can be mutated between the time of check (TOC) and the time of use (TOU).
```yaml
name: Comment Triggered Test
on:
issue_comment:
types: [created]
jobs:
benchmark:
name: Integration Tests
if: ${{ github.event.issue.pull_request && contains(fromJson('["MEMBER", "OWNER"]'), github.event.comment.author_association) && startsWith(github.event.comment.body, '/run-tests ') }}
permissions: "write-all"
runs-on: [ubuntu-latest]
steps:
- name: Get PR branch
uses: xt0rted/pull-request-comment-branch@v2
id: comment-branch
- name: Checkout PR branch
uses: actions/checkout@v3
with:
ref: ${{ steps.comment-branch.outputs.head_ref }}
- run: ./cmd
```
### Correct Usage (Issue Ops)
In the following example, the workflow checks if the latest commit of the Pull Request head was commited **before** the comment on the Pull Request, therefore ensuring that it was not mutated after the check.
```yaml
name: Comment Triggered Test
on:
issue_comment:
types: [created]
jobs:
benchmark:
name: Integration Tests
if: ${{ github.event.issue.pull_request && contains(fromJson('["MEMBER", "OWNER"]'), github.event.comment.author_association) && startsWith(github.event.comment.body, '/run-tests ') }}
permissions: "write-all"
runs-on: [ubuntu-latest]
steps:
- name: Get PR Info
id: pr
env:
PR_NUMBER: ${{ github.event.issue.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
COMMENT_AT: ${{ github.event.comment.created_at }}
run: |
pr="$(gh api /repos/${GH_REPO}/pulls/${PR_NUMBER})"
head_sha="$(echo "$pr" | jq -r .head.sha)"
pushed_at="$(echo "$pr" | jq -r .pushed_at)"
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
exit 1
fi
echo "head_sha=$head_sha" >> $GITHUB_OUTPUT
- name: Checkout PR branch
uses: actions/checkout@v3
with:
ref: ${{ steps.pr.outputs.head_sha }}
- run: ./cmd
```
### Incorrect Usage (Deployment Environment Approval)
The following workflow uses a Deployment Environment which may be configured to require an approval. However, it check outs the code pointed to by the Pull Request branch reference. At attacker could submit legitimate code for review and then change it once it gets approved.
```yml
on:
pull_request_target:
types: [Created]
jobs:
test:
environment: NeedsApproval
runs-on: ubuntu-latest
steps:
- name: Checkout from PR branch
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
- run: ./cmd
```
### Correct Usage (Deployment Environment Approval)
Use inmutable references (Commit SHA) to make sure that the reviewd code does not change between the check and the use.
```yml
on:
pull_request_target:
types: [Created]
jobs:
test:
environment: NeedsApproval
runs-on: ubuntu-latest
steps:
- name: Checkout from PR branch
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
- run: ./cmd
```
### Incorrect Usage (Label Gates)
The following workflow uses a Deployment Environment which may be configured to require an approval. However, it check outs the code pointed to by the Pull Request branch reference. At attacker could submit legitimate code for review and then change it once it gets approved.
```yaml
on:
pull_request_target:
types: [labeled]
jobs:
test:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'safe-to-test')
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- run: ./cmd
```
### Correct Usage (Label Gates)
Use inmutable references (Commit SHA) to make sure that the reviewd code does not change between the check and the use.
```yaml
on:
pull_request_target:
types: [labeled]
jobs:
test:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'safe-to-test')
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- run: ./cmd
```
## References
- [ActionsTOCTOU](https://github.com/AdnaneKhan/ActionsTOCTOU)

View File

@@ -0,0 +1,168 @@
# Untrusted Checkout TOCTOU
## Description
Untrusted Checkout is protected by a security check but the checked-out branch can be changed after the check.
## Recommendations
Verify that the code has not been modified after the security check. This may be achieved differently depending on the type of check:
- Issue Ops: Verify that Commit containing the code to be executed was commited **before** then date the of the comment.
- Deployment Environment Approval: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
- Label Gates: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
## Examples
### Incorrect Usage (Issue Ops)
The following workflow runs untrusted code after either a member or admin of the repository comments on a Pull Request with the text `/run-tests`. Although it may seem secure, the workflow is checking out a mutable reference (`${{ steps.comment-branch.outputs.head_ref }}`) and therefore the code can be mutated between the time of check (TOC) and the time of use (TOU).
```yaml
name: Comment Triggered Test
on:
issue_comment:
types: [created]
jobs:
benchmark:
name: Integration Tests
if: ${{ github.event.issue.pull_request && contains(fromJson('["MEMBER", "OWNER"]'), github.event.comment.author_association) && startsWith(github.event.comment.body, '/run-tests ') }}
permissions: "write-all"
runs-on: [ubuntu-latest]
steps:
- name: Get PR branch
uses: xt0rted/pull-request-comment-branch@v2
id: comment-branch
- name: Checkout PR branch
uses: actions/checkout@v3
with:
ref: ${{ steps.comment-branch.outputs.head_ref }}
- run: ./cmd
```
### Correct Usage (Issue Ops)
In the following example, the workflow checks if the latest commit of the Pull Request head was commited **before** the comment on the Pull Request, therefore ensuring that it was not mutated after the check.
```yaml
name: Comment Triggered Test
on:
issue_comment:
types: [created]
jobs:
benchmark:
name: Integration Tests
if: ${{ github.event.issue.pull_request && contains(fromJson('["MEMBER", "OWNER"]'), github.event.comment.author_association) && startsWith(github.event.comment.body, '/run-tests ') }}
permissions: "write-all"
runs-on: [ubuntu-latest]
steps:
- name: Get PR Info
id: pr
env:
PR_NUMBER: ${{ github.event.issue.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
COMMENT_AT: ${{ github.event.comment.created_at }}
run: |
pr="$(gh api /repos/${GH_REPO}/pulls/${PR_NUMBER})"
head_sha="$(echo "$pr" | jq -r .head.sha)"
pushed_at="$(echo "$pr" | jq -r .pushed_at)"
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
exit 1
fi
echo "head_sha=$head_sha" >> $GITHUB_OUTPUT
- name: Checkout PR branch
uses: actions/checkout@v3
with:
ref: ${{ steps.pr.outputs.head_sha }}
- run: ./cmd
```
### Incorrect Usage (Deployment Environment Approval)
The following workflow uses a Deployment Environment which may be configured to require an approval. However, it check outs the code pointed to by the Pull Request branch reference. At attacker could submit legitimate code for review and then change it once it gets approved.
```yml
on:
pull_request_target:
types: [Created]
jobs:
test:
environment: NeedsApproval
runs-on: ubuntu-latest
steps:
- name: Checkout from PR branch
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
- run: ./cmd
```
### Correct Usage (Deployment Environment Approval)
Use inmutable references (Commit SHA) to make sure that the reviewd code does not change between the check and the use.
```yml
on:
pull_request_target:
types: [Created]
jobs:
test:
environment: NeedsApproval
runs-on: ubuntu-latest
steps:
- name: Checkout from PR branch
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
- run: ./cmd
```
### Incorrect Usage (Label Gates)
The following workflow uses a Deployment Environment which may be configured to require an approval. However, it check outs the code pointed to by the Pull Request branch reference. At attacker could submit legitimate code for review and then change it once it gets approved.
```yaml
on:
pull_request_target:
types: [labeled]
jobs:
test:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'safe-to-test')
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- run: ./cmd
```
### Correct Usage (Label Gates)
Use inmutable references (Commit SHA) to make sure that the reviewd code does not change between the check and the use.
```yaml
on:
pull_request_target:
types: [labeled]
jobs:
test:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'safe-to-test')
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- run: ./cmd
```
## References
- [ActionsTOCTOU](https://github.com/AdnaneKhan/ActionsTOCTOU)

View File

@@ -0,0 +1,63 @@
# If Condition Always Evaluates to True
## Description
GitHub Workflow Expressions (`${{ ... }}`) used in the `if` condition of jobs or steps must not contain extra characters or spaces. Otherwise, the condition is invariably evaluated to `true`.
When an `if` condition erroneously evaluates to `true`, unintended steps may be executed, leading to logic bugs and potentially exposing parts of the workflow designed to run only in secure scenarios. This behavior subverts the intended conditional logic of the workflow, leading to potential security vulnerabilities and unintentional consequences.
## Recommendation
To avoid the vulnerability where an `if` condition always evaluates to `true`, it is crucial to eliminate any extra characters or spaces in your GitHub Actions expressions:
1. Do not use Workflow Expressions in `if` conditions.
2. Avoid multiline or spaced-out conditional expressions that might inadvertently introduce unwanted characters or formatting.
3. Test the workflow to ensure the `if` conditions behave as expected under different scenarios.
## Examples
### Correct Usage
1. Do not use Workflow Expressions:
```yaml
if: steps.checks.outputs.safe_to_run == true
if: |-
steps.checks.outputs.safe_to_run == true
if: |
steps.checks.outputs.safe_to_run == true
```
2. If using Workflow Expressions, ensure the `if` condition is formatted correctly without extra spaces or characters:
```yaml
if: ${{ steps.checks.outputs.safe_to_run == true }}
if: |-
${{ steps.checks.outputs.safe_to_run == true }}
```
### Incorrect Usage
1. Do not mix Workflow Expressions with un-delimited expressions:
```yaml
if: ${{ steps.checks.outputs.safe_to_run }} == true
```
2. Do not include trailing new lines or spaces:
```yaml
if: |
${{ steps.checks.outputs.safe_to_run == true }}
if: >
${{ steps.checks.outputs.safe_to_run == true }}
if: " ${{ steps.checks.outputs.safe_to_run == true }}"
if: |+
${{ steps.checks.outputs.safe_to_run == true }}
if: >+
${{ steps.checks.outputs.safe_to_run == true }}
```
## References
- [Expression Always True Github Issue](https://github.com/actions/runner/issues/1173)

View File

@@ -0,0 +1,72 @@
# Artifact poisoning
## Description
The workflow download artifacts that may be poisoned by an attacker in previously triggered workflows. If the contents of these artifacts are not correctly extracted, stored and verified, they may lead to repository compromise if untrusted code gets executed in a privileged job.
## Recommendations
- Always consider artifacts content as untrusted.
- Extract the contents of artifacts to a temporary folder so they cannot override existing files.
- Verify the contents of the artifacts downloaded. If an artifact is expected to contain a numeric value, verify it before using it.
## Examples
### Incorrect Usage
The following workflow downloads an artifact that can potentially be controlled by an attacker and then runs an script from the runner workspace. Because the `dawidd6/action-download-artifact` by default downloads and extracts the contents of the artifacts overriding existing files. An attacker will be able to override the contents of `cmd.sh` and gain code execution when this file gets executed.
```yaml
name: Insecure Workflow
on:
workflow_run:
workflows: ["Prev"]
types:
- completed
jobs:
Download:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dawidd6/action-download-artifact@v2
with:
name: pr_number
- name: Run command
run: |
sh cmd.sh
```
### Correct Usage
The following example, correctly creates a temporary directory and stores the contents of the artifact there before calling `cmd.sh`.
```yaml
name: Insecure Workflow
on:
workflow_run:
workflows: ["Prev"]
types:
- completed
jobs:
Download:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: mkdir -p ${{ runner.temp }}/artifacts/
- uses: dawidd6/action-download-artifact@v2
with:
name: pr_number
path: ${{ runner.temp }}/artifacts/
- name: Run command
run: |
sh cmd.sh
```
## References
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)

View File

@@ -0,0 +1,72 @@
# Artifact poisoning
## Description
The workflow download artifacts that may be poisoned by an attacker in previously triggered workflows. If the contents of these artifacts are not correctly extracted, stored and verified, they may lead to repository compromise if untrusted code gets executed in a privileged job.
## Recommendations
- Always consider artifacts content as untrusted.
- Extract the contents of artifacts to a temporary folder so they cannot override existing files.
- Verify the contents of the artifacts downloaded. If an artifact is expected to contain a numeric value, verify it before using it.
## Examples
### Incorrect Usage
The following workflow downloads an artifact that can potentially be controlled by an attacker and then runs an script from the runner workspace. Because the `dawidd6/action-download-artifact` by default downloads and extracts the contents of the artifacts overriding existing files. An attacker will be able to override the contents of `cmd.sh` and gain code execution when this file gets executed.
```yaml
name: Insecure Workflow
on:
workflow_run:
workflows: ["Prev"]
types:
- completed
jobs:
Download:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dawidd6/action-download-artifact@v2
with:
name: pr_number
- name: Run command
run: |
sh cmd.sh
```
### Correct Usage
The following example, correctly creates a temporary directory and stores the contents of the artifact there before calling `cmd.sh`.
```yaml
name: Insecure Workflow
on:
workflow_run:
workflows: ["Prev"]
types:
- completed
jobs:
Download:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: mkdir -p ${{ runner.temp }}/artifacts/
- uses: dawidd6/action-download-artifact@v2
with:
name: pr_number
path: ${{ runner.temp }}/artifacts/
- name: Run command
run: |
sh cmd.sh
```
## References
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)

View File

@@ -0,0 +1,27 @@
# Unpinned tag for 3rd party Action in workflow
## Description
Using a tag for a 3rd party Action that is not pinned to a commit can lead to executing an untrusted Action through a supply chain attack.
## Recommendations
Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. When selecting a SHA, you should verify it is from the action's repository and not a repository fork.
## Examples
### Incorrect Usage
```yaml
- uses: tj-actions/changed-files@v44
```
### Correct Usage
```yaml
- uses: tj-actions/changed-files@c65cd883420fd2eb864698a825fc4162dd94482c # v44
```
## References
- [Using third-party actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions)

View File

@@ -0,0 +1,137 @@
# Execution of Untrusted Checkedout Code
## Description
GitHub workflows can be triggered through various repository events, including incoming pull requests (PRs) or comments on Issues/PRs. A potentially dangerous misuse of the triggers such as `pull_request_target` or `issue_comment` followed by an explicit checkout of untrusted code (Pull Request HEAD) may lead to repository compromise if untrusted code gets executed in a privileged job.
## Recommendations
- Avoid using `pull_request_target` unless necessary.
- Employ unprivileged `pull_request` workflows followed by `workflow_run` for privileged operations.
- Use labels like `safe to test` to vet PRs and manage the execution context appropriately.
The best practice is to handle the potentially untrusted pull request via the **pull_request** trigger so that it is isolated in an unprivileged environment. The workflow processing the pull request should then store any results like code coverage or failed/passed tests in artifacts and exit. A second workflow should get triggered by the completion of the first one using `workflow_run` trigger event and access to repository secrets, so that it can download the artifacts and make any necessary modifications to the repository or interact with third party services that require repository secrets (e.g. API tokens).
The artifacts downloaded from the first workflow should be considered untrusted and verified.
## Examples
### Incorrect Usage
The following workflow checks-out untrusted code in a privileged context and runs user-controlled code (in this case package.json scripts) which will grant privileged access to the attacker:
```yaml
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!
```
### Correct Usage
An example shows how to use two workflows: one for processing the untrusted PR and the other for using the results in a safe context.
**ReceivePR.yml** (untrusted PR handling with artifact creation):
```yaml
name: Receive PR
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: /bin/bash ./build.sh
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- uses: actions/upload-artifact@v2
with:
name: pr
path: pr/
```
**CommentPR.yml** (processing artifacts with privileged access):
```yaml
name: Comment on the pull request
on:
workflow_run:
workflows: ["Receive PR"]
types:
- completed
jobs:
upload:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: "Download artifact"
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr";
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
- run: |
mkdir -p tmp
unzip -d tmp/ pr.zip
- name: "Comment on PR"
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
var issue_number = Number(fs.readFileSync('./tmp/NR'));
// Verify that the file contains a numeric value
const contains_numeric = /\d/.test(issue_number);
if (contains_numeric) {
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: 'Everything is OK. Thank you for the PR!'
});
}
```
## References
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)

View File

@@ -0,0 +1,137 @@
# Execution of Untrusted Checkedout Code
## Description
GitHub workflows can be triggered through various repository events, including incoming pull requests (PRs) or comments on Issues/PRs. A potentially dangerous misuse of the triggers such as `pull_request_target` or `issue_comment` followed by an explicit checkout of untrusted code (Pull Request HEAD) may lead to repository compromise if untrusted code gets executed in a privileged job.
## Recommendations
- Avoid using `pull_request_target` unless necessary.
- Employ unprivileged `pull_request` workflows followed by `workflow_run` for privileged operations.
- Use labels like `safe to test` to vet PRs and manage the execution context appropriately.
The best practice is to handle the potentially untrusted pull request via the **pull_request** trigger so that it is isolated in an unprivileged environment. The workflow processing the pull request should then store any results like code coverage or failed/passed tests in artifacts and exit. A second workflow should get triggered by the completion of the first one using `workflow_run` trigger event and access to repository secrets, so that it can download the artifacts and make any necessary modifications to the repository or interact with third party services that require repository secrets (e.g. API tokens).
The artifacts downloaded from the first workflow should be considered untrusted and verified.
## Examples
### Incorrect Usage
The following workflow checks-out untrusted code in a privileged context and runs user-controlled code (in this case package.json scripts) which will grant privileged access to the attacker:
```yaml
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!
```
### Correct Usage
An example shows how to use two workflows: one for processing the untrusted PR and the other for using the results in a safe context.
**ReceivePR.yml** (untrusted PR handling with artifact creation):
```yaml
name: Receive PR
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: /bin/bash ./build.sh
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- uses: actions/upload-artifact@v2
with:
name: pr
path: pr/
```
**CommentPR.yml** (processing artifacts with privileged access):
```yaml
name: Comment on the pull request
on:
workflow_run:
workflows: ["Receive PR"]
types:
- completed
jobs:
upload:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: "Download artifact"
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr";
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
- run: |
mkdir -p tmp
unzip -d tmp/ pr.zip
- name: "Comment on PR"
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
var issue_number = Number(fs.readFileSync('./tmp/NR'));
// Verify that the file contains a numeric value
const contains_numeric = /\d/.test(issue_number);
if (contains_numeric) {
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: 'Everything is OK. Thank you for the PR!'
});
}
```
## References
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)

View File

@@ -0,0 +1,137 @@
# Execution of Untrusted Checkedout Code
## Description
GitHub workflows can be triggered through various repository events, including incoming pull requests (PRs) or comments on Issues/PRs. A potentially dangerous misuse of the triggers such as `pull_request_target` or `issue_comment` followed by an explicit checkout of untrusted code (Pull Request HEAD) may lead to repository compromise if untrusted code gets executed in a privileged job.
## Recommendations
- Avoid using `pull_request_target` unless necessary.
- Employ unprivileged `pull_request` workflows followed by `workflow_run` for privileged operations.
- Use labels like `safe to test` to vet PRs and manage the execution context appropriately.
The best practice is to handle the potentially untrusted pull request via the **pull_request** trigger so that it is isolated in an unprivileged environment. The workflow processing the pull request should then store any results like code coverage or failed/passed tests in artifacts and exit. A second workflow should get triggered by the completion of the first one using `workflow_run` trigger event and access to repository secrets, so that it can download the artifacts and make any necessary modifications to the repository or interact with third party services that require repository secrets (e.g. API tokens).
The artifacts downloaded from the first workflow should be considered untrusted and verified.
## Examples
### Incorrect Usage
The following workflow checks-out untrusted code in a privileged context and runs user-controlled code (in this case package.json scripts) which will grant privileged access to the attacker:
```yaml
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!
```
### Correct Usage
An example shows how to use two workflows: one for processing the untrusted PR and the other for using the results in a safe context.
**ReceivePR.yml** (untrusted PR handling with artifact creation):
```yaml
name: Receive PR
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: /bin/bash ./build.sh
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- uses: actions/upload-artifact@v2
with:
name: pr
path: pr/
```
**CommentPR.yml** (processing artifacts with privileged access):
```yaml
name: Comment on the pull request
on:
workflow_run:
workflows: ["Receive PR"]
types:
- completed
jobs:
upload:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: "Download artifact"
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr";
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
- run: |
mkdir -p tmp
unzip -d tmp/ pr.zip
- name: "Comment on PR"
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
var issue_number = Number(fs.readFileSync('./tmp/NR'));
// Verify that the file contains a numeric value
const contains_numeric = /\d/.test(issue_number);
if (contains_numeric) {
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: 'Everything is OK. Thank you for the PR!'
});
}
```
## References
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)