mirror of
https://github.com/github/codeql.git
synced 2026-05-04 05:05:12 +02:00
New Descriptions
This commit is contained in:
37
ql/src/Security/CWE-077/EnvPathInjectionCritical.md
Normal file
37
ql/src/Security/CWE-077/EnvPathInjectionCritical.md
Normal 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)
|
||||
37
ql/src/Security/CWE-077/EnvPathInjectionMedium.md
Normal file
37
ql/src/Security/CWE-077/EnvPathInjectionMedium.md
Normal 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)
|
||||
117
ql/src/Security/CWE-077/EnvVarInjectionCritical.md
Normal file
117
ql/src/Security/CWE-077/EnvVarInjectionCritical.md
Normal 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)
|
||||
117
ql/src/Security/CWE-077/EnvVarInjectionMedium.md
Normal file
117
ql/src/Security/CWE-077/EnvVarInjectionMedium.md
Normal 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)
|
||||
41
ql/src/Security/CWE-088/ArgumentInjectionCritical.md
Normal file
41
ql/src/Security/CWE-088/ArgumentInjectionCritical.md
Normal 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/)
|
||||
41
ql/src/Security/CWE-088/ArgumentInjectionMedium.md
Normal file
41
ql/src/Security/CWE-088/ArgumentInjectionMedium.md
Normal 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/)
|
||||
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
13
ql/src/Security/CWE-1395/UseOfKnownVulnerableAction.md
Normal file
13
ql/src/Security/CWE-1395/UseOfKnownVulnerableAction.md
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
57
ql/src/Security/CWE-285/ImproperAccessControl.md
Normal file
57
ql/src/Security/CWE-285/ImproperAccessControl.md
Normal 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)
|
||||
52
ql/src/Security/CWE-312/ExcessiveSecretsExposure.md
Normal file
52
ql/src/Security/CWE-312/ExcessiveSecretsExposure.md
Normal 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)
|
||||
37
ql/src/Security/CWE-312/UnmaskedSecretExposure.md
Normal file
37
ql/src/Security/CWE-312/UnmaskedSecretExposure.md
Normal 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)
|
||||
83
ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.md
Normal file
83
ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.md
Normal 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 other’s 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/)
|
||||
101
ql/src/Security/CWE-349/CachePoisoningViaDirectCache.md
Normal file
101
ql/src/Security/CWE-349/CachePoisoningViaDirectCache.md
Normal 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 other’s 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/)
|
||||
85
ql/src/Security/CWE-349/CachePoisoningViaPoisonableStep.md
Normal file
85
ql/src/Security/CWE-349/CachePoisoningViaPoisonableStep.md
Normal 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 other’s 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/)
|
||||
168
ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.md
Normal file
168
ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.md
Normal 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)
|
||||
168
ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUMedium.md
Normal file
168
ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUMedium.md
Normal 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)
|
||||
63
ql/src/Security/CWE-571/ExpressionIsAlwaysTrue.md
Normal file
63
ql/src/Security/CWE-571/ExpressionIsAlwaysTrue.md
Normal 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)
|
||||
72
ql/src/Security/CWE-829/ArtifactPoisoningCritical.md
Normal file
72
ql/src/Security/CWE-829/ArtifactPoisoningCritical.md
Normal 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/)
|
||||
72
ql/src/Security/CWE-829/ArtifactPoisoningMedium.md
Normal file
72
ql/src/Security/CWE-829/ArtifactPoisoningMedium.md
Normal 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/)
|
||||
27
ql/src/Security/CWE-829/UnpinnedActionsTag.md
Normal file
27
ql/src/Security/CWE-829/UnpinnedActionsTag.md
Normal 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)
|
||||
137
ql/src/Security/CWE-829/UntrustedCheckoutCritical.md
Normal file
137
ql/src/Security/CWE-829/UntrustedCheckoutCritical.md
Normal 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/)
|
||||
137
ql/src/Security/CWE-829/UntrustedCheckoutHigh.md
Normal file
137
ql/src/Security/CWE-829/UntrustedCheckoutHigh.md
Normal 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/)
|
||||
137
ql/src/Security/CWE-829/UntrustedCheckoutMedium.md
Normal file
137
ql/src/Security/CWE-829/UntrustedCheckoutMedium.md
Normal 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/)
|
||||
Reference in New Issue
Block a user