GitHub Actionsのrunブロックにおけるコマンドインジェクション

GitHub Actionsの信頼できないイベント値は、シェルコードへ直接埋め込まずデータとして渡してください。

説明

IssueやPRのタイトル・本文、コメント、コミットメッセージ、作成者情報は、外部ユーザーが制御できる場合があります。${{ }}式でこれらをrunのコマンドへ直接埋め込むと、シェルの実行前にコードへ置換され、コマンドインジェクションにつながる可能性があります。式を引用符で囲むだけでは不十分です。

想定される影響

  • 注入されたコマンドが、実行環境のファイルやジョブに渡されたトークン・シークレットへアクセスする可能性があります。
  • その権限によっては、成果物の改ざんやリポジトリ操作の悪用につながります。

対処方法

  • 外部の値をインラインのシェルコードへ直接埋め込まないでください。必要な値はステップのenvで渡し、シェルでは"$VALUE"のように引用した変数として扱ってください。evalなどで再びコードとして評価しないでください。
  • 想定する形式を検証し、コマンドの引数を分けてください。GITHUB_TOKENとシークレットへのアクセスも必要な範囲に限定してください。
  • pull_request_targetなど権限の強いワークフローでは、信頼できないPRのコードをチェックアウトしたり実行したりしないでください。

例

最初の例はPR本文をコマンドへ埋め込みます。二つ目は固定されたリポジトリ内のテストコマンドを実行する別の例で、信頼できるベースブランチのコードと必要なテストスクリプトがあることを前提とします。

変更前

yaml
name: Pull Request Workflow

on:
  pull_request_target:
    types:
      - opened

jobs:
  process_pull_request:
    runs-on: ubuntu-latest
    steps:
      - name: Echo Pull Request Body
        run: |
          echo "Pull Request Body: ${{ github.event.pull_request.body }}"

PR本文が実行するシェルコードの一部になります。攻撃者が制御できる本文を二重引用符で囲んでも、コード注入の危険は残ります。

変更後

yaml
name: check-go-coverage

on:
  pull_request_target:
    branches: [master]

jobs:
  coverage:
    name: Check Go coverage
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up Go 1.22.x
        uses: actions/setup-go@v5
        with:
          go-version: 1.22.x
      - name: Run test metrics script
        id: testcov
        run: |
          make test-coverage-report | tee test-results
          echo "coverage=$(cat test-results | grep "Total coverage: " test-results | cut -d ":" -f 2 | bc)" >> $GITHUB_ENV

これらのコマンドはPR本文を直接埋め込みません。後から外部の値を追加する場合もデータとして渡し、強い権限で信頼できないコードを実行しないでください。

参考資料