diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md index 563608674..aa6e253fb 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md @@ -4,18 +4,18 @@ ## リスクの理解 -GitHub Actions はステップが実行される前に ${{ ... }} の式をレンダリングします。レンダリングされた値はステップのプログラムに貼り付けられます(run ステップならシェルスクリプト)。run: 内に信頼できない入力を直接埋め込むと、攻撃者がシェルプログラムの一部を制御でき、任意のコマンドを実行される可能性があります。 +GitHub Actions は step が実行される前に expressions ${{ ... }} をレンダリングします。レンダリングされた値は step の program に貼り付けられます(run step の場合は shell script)。もし信頼できない input を run: の中に直接 interpolate すると、attacker が shell program の一部を制御でき、任意の commands を実行できます。 -Docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions and contexts/functions: https://docs.github.com/en/actions/learn-github-actions/contexts +Docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions と contexts/functions: https://docs.github.com/en/actions/learn-github-actions/contexts -重要なポイント: -- レンダリングは実行前に行われます。run スクリプトはすべての式が解決された状態で生成され、その後シェルで実行されます。 -- 多くのコンテキストは、トリガーイベント(issues、PRs、comments、discussions、forks、stars など)に応じてユーザーが制御するフィールドを含みます。詳細は untrusted input reference を参照してください: https://securitylab.github.com/resources/github-actions-untrusted-input/ -- run: 内のシェルのクォートは信頼できる防御策ではありません。インジェクションはテンプレートのレンダリング段階で発生するため、攻撃者はクォートを破ったり、巧妙な入力で演算子を注入したりできます。 +Key points: +- Rendering は execution の前に行われます。run script は、すべての expressions が解決された状態で生成され、その後 shell によって実行されます。 +- 多くの contexts には、triggering event に応じて user-controlled fields が含まれます(issues、PRs、comments、discussions、forks、stars など)。untrusted input の reference を参照してください: https://securitylab.github.com/resources/github-actions-untrusted-input/ +- run: 内での shell quoting は reliable な defense ではありません。なぜなら injection は template rendering stage で発生するからです。attacker は crafted input を使って quotes から抜け出したり、operators を注入したりできます。 -## 脆弱なパターン → runner上での RCE +## Vulnerable pattern → RCE on runner -脆弱なワークフロー(誰かが新しい issue を開いたときにトリガーされます): +Vulnerable workflow (triggered when someone opens a new issue): ```yaml name: New Issue Created on: @@ -36,20 +36,56 @@ with: github_token: ${{ secrets.GITHUB_TOKEN }} labels: new ``` -攻撃者が $(id) をタイトルにした issue を開くと、レンダリングされたステップは次のようになります: +攻撃者が $(id) というタイトルの issue を開くと、レンダリングされた step は次のようになります: ```sh echo "New issue $(id) created" ``` -コマンド置換は runner 上で id を実行します。出力例: +コマンド置換は runner 上で id を実行する。出力例: ``` New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created ``` -なぜ引用はあなたを守れないのか: -- 式はまず展開され、その結果できたスクリプトが実行されます。信頼できない値に $(...), `;`, `"`/`'`, または改行が含まれていると、引用していてもプログラム構造を変更される可能性があります。 +引用しても防げない理由: +- 式は先にレンダリングされ、その後に生成されたスクリプトが実行されます。信頼できない値に $(...)、`;`、`"`/`'`、または改行が含まれていると、引用していてもプログラム構造を変更できます。 -## 安全なパターン (shell variables via env) +## Comment-state confusion: spoofed bot comments → shell injection -Correct mitigation: copy untrusted input into an environment variable, then use native shell expansion ($VAR) in the run script. Do not re-embed with ${{ ... }} inside the command. +危険な変種は、workflow が **comments を検索し、その後返された comment を信頼された automation state として扱う** 場合に現れます。たとえば、`peter-evans/find-comment` は `body-includes` で検索し、一致した `comment-body` を step output として公開できます。workflow が `comment-author` も制限しない場合、コメントできる任意の user は bot から期待される marker text を偽装できます。 +```yaml +- uses: peter-evans/find-comment@v4 +id: fc +with: +issue-number: ${{ github.event.issue.number }} +body-includes: "Opened a new issue in org/repo:" +``` +その出力が後で shell syntax に埋め込まれると、元のソースが「単なる comment」だったとしても、workflow は exploit 可能になります: +```yaml +- run: | +if [ '${{ steps.fc.outputs.comment-body }}' = '' ]; then +echo "new issue needed" +fi +``` +攻撃者は、以下の両方を満たすコメントを投稿できます: +- 検索された marker string に一致する +- `' ]; ; if [ 'x` のような shell-breaking content を含む + +GitHub が `${{ ... }}` をレンダリングした後、Bash は data ではなく攻撃者が制御する syntax を受け取ります。これにより、**2段階の exploit** が成立します: +1. **Provenance confusion**: workflow が attacker comments を bot state と誤認する。 +2. **Script injection**: 返された `comment-body` が `run:` に貼り付けられて実行される。 + +### bot comments に対する TOCTOU race + +正規の bot comment が後の step でしか作成されない場合、攻撃者は先に spoofed comment を投稿して race できることがあります。search action が real bot comment が存在する前(または選択される前)に attacker の comment を返すと、低権限の public commenter が `issue_comment`/`issue` workflow を privileged runner execution に変えられます。 + +### comment-driven automation のより安全なパターン + +- `find-comment` を使う場合は、**content と provenance の両方**(`comment-author`、repository/App identity、または他の強固な binding)を必須にする。 +- 同じ state をより安全に保持できるなら、comments を state として使わない。label、artifact、issue field、external datastore を検討する。 +- `comment-body`、issue titles、labels、またはそれらから派生した workflow output を `run:` に直接貼り付けない。 +- comment text をどうしても使う必要があるなら、`env:` か file 経由で渡し、data としてのみ扱う。 + +## Safe pattern (shell variables via env) + +正しい mitigation: 信頼できない input を environment variable にコピーし、run script では native shell expansion ($VAR) を使う。command 内で ${{ ... }} を再埋め込みしない。 ```yaml # safe jobs: @@ -63,31 +99,39 @@ run: | echo "New issue $TITLE created" ``` 注意: -- Avoid using ${{ env.TITLE }} inside run:. That reintroduces template rendering back into the command and brings the same injection risk. -- Prefer passing untrusted inputs via env: mapping and reference them with $VAR in run:. +- `run:` 内で ${{ env.TITLE }} を使わないでください。これにより template rendering がコマンド内に再導入され、同じ injection リスクが発生します。 +- 信頼できない入力は `env:` mapping で渡し、`run:` では `$VAR` で参照するのが望ましいです。 -## 読者がトリガーできるサーフェス(未検証として扱う) +## Reader-triggerable surfaces (untrusted として扱う) -public repositories に対して読み取りのみの権限しか持たないアカウントでも、多くのイベントをトリガーできます。これらのイベントに由来するコンテキスト内の任意のフィールドは、反証されない限り攻撃者制御下にあると見なすべきです。例: +public repositories に対して read permission しかないアカウントでも、多くのイベントを trigger できます。これらの events から派生した contexts 内の任意の field は、別途証明されない限り attacker-controlled とみなす必要があります。例: - issues, issue_comment - discussion, discussion_comment (orgs can restrict discussions) - pull_request, pull_request_review, pull_request_review_comment -- pull_request_target (dangerous if misused, runs in base repo context) -- fork (anyone can fork public repos) -- watch (starring a repo) -- Indirectly via workflow_run/workflow_call chains +- pull_request_target (誤用すると dangerous, base repo context で実行される) +- fork (誰でも public repos を fork できる) +- watch (repo に star を付ける) +- workflow_run/workflow_call chains 経由で間接的に -どの特定のフィールドが攻撃者制御下にあるかはイベントごとに異なります。詳細は GitHub Security Lab の未検証入力に関するガイドを参照してください: https://securitylab.github.com/resources/github-actions-untrusted-input/ +どの specific fields が attacker-controlled かは event-specific です。GitHub Security Lab の untrusted input guide を参照してください: https://securitylab.github.com/resources/github-actions-untrusted-input/ -## 実用的なヒント +## ターゲット repo に触れずに local validation する -- Minimize use of expressions inside run:. Prefer env: mapping + $VAR. -- 入力を変換する必要がある場合は、シェル内で安全なツール(printf %q、jq -r 等)を使って行い、始点はシェル変数にしてください。 -- ブランチ名、PR titles、ユーザー名、ラベル、ディスカッションタイトル、PR head refs をスクリプト、コマンドラインフラグ、またはファイルパスに挿入する際は特に慎重になってください。 -- 再利用可能な workflows や composite actions に対しても同じパターンを適用してください: env にマップしてから $VAR を参照する。 +[`act`](https://github.com/nektos/act) を使うと、多くの GitHub Actions script injections を安全に再現できます: synthetic event JSON を生成し、vulnerable workflow を local で実行し、external action の output を制御された value に置き換えます (たとえば mocked `comment-body`)。これは payload structure の debug、inject された text がまだ valid Bash syntax を保っているかの確認、そして live test の前に harmless な canary exfiltration を確認するのに役立ちます。 + +## 実践的な tips + +- `run:` 内での expressions の使用は最小限にしてください。`env:` mapping + `$VAR` を優先します。 +- 入力を変換する必要がある場合は、shell 内で安全な tools (printf %q, jq -r, etc.) を使って行い、必ず shell variable から開始してください。 +- branch names, PR titles, usernames, labels, discussion titles, PR head refs を scripts, command-line flags, file paths に埋め込むときは特に注意してください。 +- reusable workflows と composite actions でも同じ pattern を適用してください: env に map してから `$VAR` を参照します。 ## References +- [Find Comment, Get Shell: Command Injection in dbt’s GitHub Actions](https://landh.tech/blog/20260701-find-comment-get-shell) +- [peter-evans/find-comment](https://github.com/peter-evans/find-comment) +- [GHSL-2023-109: GitHub Actions command injection in a TDesign Vue Next workflow](https://securitylab.github.com/advisories/GHSL-2023-109_TDesign_Vue_Next/) +- [nektos/act](https://github.com/nektos/act) - [GitHub Actions: A Cloudy Day for Security - Part 1](https://binarysecurity.no/posts/2025/08/securing-gh-actions-part1) - [GitHub workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) - [Contexts and expression syntax](https://docs.github.com/en/actions/learn-github-actions/contexts)