yaml.github-actions.security.run-shell-injection.run-shell-injection

Author
81
Download Count*
License
Using variable interpolation ${{...}}
with github
context data in a run:
step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github
context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env:
to store the data and use the environment variable in the run:
script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".
Run Locally
Run in CI
Defintion
rules:
- id: run-shell-injection
languages:
- yaml
message: 'Using variable interpolation `${{...}}` with `github` context data in
a `run:` step could allow an attacker to inject their own code into the
runner. This would allow them to steal secrets and code. `github` context
data can have arbitrary user input and should be treated as untrusted.
Instead, use an intermediate environment variable with `env:` to store the
data and use the environment variable in the `run:` script. Be sure to use
double-quotes the environment variable, like this: "$ENVVAR".'
metadata:
category: security
cwe:
- "CWE-78: Improper Neutralization of Special Elements used in an OS
Command ('OS Command Injection')"
owasp:
- A01:2017 - Injection
- A03:2021 - Injection
references:
- https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#understanding-the-risk-of-script-injections
- https://securitylab.github.com/research/github-actions-untrusted-input/
technology:
- github-actions
cwe2022-top25: true
cwe2021-top25: true
subcategory:
- vuln
likelihood: HIGH
impact: HIGH
confidence: HIGH
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
patterns:
- pattern-inside: "steps: [...]"
- pattern-inside: |
- run: ...
...
- pattern: "run: $SHELL"
- metavariable-pattern:
language: generic
metavariable: $SHELL
patterns:
- pattern-either:
- pattern: ${{ github.event.issue.title }}
- pattern: ${{ github.event.issue.body }}
- pattern: ${{ github.event.pull_request.title }}
- pattern: ${{ github.event.pull_request.body }}
- pattern: ${{ github.event.comment.body }}
- pattern: ${{ github.event.review.body }}
- pattern: ${{ github.event.review_comment.body }}
- pattern: ${{ github.event.pages. ... .page_name}}
- pattern: ${{ github.event.head_commit.message }}
- pattern: ${{ github.event.head_commit.author.email }}
- pattern: ${{ github.event.head_commit.author.name }}
- pattern: ${{ github.event.commits ... .author.email }}
- pattern: ${{ github.event.commits ... .author.name }}
- pattern: ${{ github.event.pull_request.head.ref }}
- pattern: ${{ github.event.pull_request.head.label }}
- pattern: ${{ github.event.pull_request.head.repo.default_branch }}
- pattern: ${{ github.head_ref }}
- pattern: ${{ github.event.inputs ... }}
severity: ERROR
Examples
run-shell-injection.test.yaml
# Push Semgrep Docker Image
name: docker
on:
workflow_dispatch:
inputs:
message_to_print:
type: string
required: false
push:
branches:
- develop
pull_request:
paths-ignore:
- "**.md"
jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Pull previous image for caching purposes
run: |
docker pull returntocorp/semgrep:develop || :
- name: Build semgrep image with default Dockerfile
run: docker build -t returntocorp/semgrep:develop .
- name: Check the semgrep Docker image
run: ./scripts/validate-docker-build.sh returntocorp/semgrep:develop
- name: Login to DockerHub
env:
DOCKERHUB_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_PASS: ${{ secrets.DOCKER_PASSWORD }}
run: ./.github/docker-login
- name: Push semgrep image if on develop branch
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
run: docker push returntocorp/semgrep:develop
- name:
Push commit hash if PR
# Don't run when PR is from a fork
# For security, we do not autopush to docker when from PRs
# said PRs do not have access to secrets so will fail anyway but
# nicer to not have a "failing" CI job in the PR so don't even
# try if we can detect is coming from a fork
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
# ok: run-shell-injection
run: |
tag=returntocorp/semgrep:${{ github.sha }}
docker build -t "$tag" .
docker push "$tag"
# Extend the semgrep image, changing the entry point to bash and
# adding some utilities. This image is meant for internal uses
# such as benchmarks.
- name: Build and push semgrep-dev image if on develop branch
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
run: |
docker build \
-f dockerfiles/semgrep-dev.Dockerfile \
-t returntocorp/semgrep-dev:develop .
docker push returntocorp/semgrep-dev:develop
- name: Check PR title
# ruleid: run-shell-injection
run: |
title="${{ github.event.pull_request.title }}"
if [[ $title =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
exit 0
else
echo "PR title did not start with 'octocat'"
exit 1
fi
- if: github.event_name == 'push'
# ruleid: run-shell-injection
run: |
title="${{ github.event.pull_request.title }}"
if [[ $title =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
exit 0
else
echo "PR title did not start with 'octocat'"
exit 1
fi
- name: Print a message
# ruleid: run-shell-injection
run: |
echo "${{github.event.inputs.message_to_print}}"
- name: Show author email
# ruleid: run-shell-injection
run: |
echo "${{ github.event.commits.fix-bug.author.email }}"
- name: Show issue title
# ruleid: run-shell-injection
run: |
echo "${{ github.event.issue.title }}"
- name: benign
# ok: run-shell-injection
run: |
AUTH_HEADER="Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}";
HEADER="Accept: application/vnd.github.v3+json";
# cf. https://github.com/magma/magma/blob/5caf0cb5151a9b0dce05985e6cb2cdcf70b94af5/.github/workflows/unit-test-workflow.yml
- name: Download and Extract Artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ok: run-shell-injection
run: |
mkdir -p artifacts && cd artifacts
artifacts_url=${{ github.event.workflow_run.artifacts_url }}
gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact
do
IFS=$'\t' read name url <<< "$artifact"
gh api $url > "$name.zip"
unzip -d "$name" "$name.zip"
done
Short Link: https://sg.run/pkzk