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

profile photo of semgrepsemgrep
Author
81
Download Count*

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]
      vulnerability_class:
        - Command Injection
    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