Translated ['', 'src/pentesting-ci-cd/github-security/abusing-github-act

This commit is contained in:
Translator
2026-07-06 15:31:48 +00:00
parent 1869ac3fce
commit 421de0c7a8
@@ -4,18 +4,18 @@
## Comprendere il rischio
GitHub Actions valuta le espressioni ${{ ... }} prima che lo step venga eseguito. Il valore valutato viene inserito nel programma dello step (per i run steps, uno script shell). Se interpoli input non attendibile direttamente dentro run:, l'attaccante controlla parte del programma shell e può eseguire comandi arbitrari.
GitHub Actions rende le expressions ${{ ... }} prima dellesecuzione dello step. Il valore renderizzato viene incollato nel program dello step stesso (per gli step run, uno shell script). Se interpoli input non affidabile direttamente dentro run:, lattaccante controlla una parte del shell program e può eseguire command arbitrari.
Docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions and contexts/functions: https://docs.github.com/en/actions/learn-github-actions/contexts
Punti chiave:
- La valutazione (rendering) avviene prima dell'esecuzione. Lo script di run viene generato con tutte le espressioni risolte, poi eseguito dalla shell.
- Molti contexts contengono campi controllati dall'utente a seconda dell'evento che attiva il workflow (issues, PRs, comments, discussions, forks, stars, ecc.). Vedi il riferimento sull'input non attendibile: https://securitylab.github.com/resources/github-actions-untrusted-input/
- L'escaping/quoting della shell dentro run: non è una difesa affidabile, perché l'iniezione avviene nella fase di rendering del template. Gli attaccanti possono uscire dalle virgolette o iniettare operatori mediante input appositamente creato.
- Il rendering avviene prima dellesecuzione. Lo script run viene generato con tutte le expressions risolte, poi eseguito dalla shell.
- Molti contexts contengono campi controllati dallutente a seconda dellevento che li attiva (issues, PRs, comments, discussions, forks, stars, ecc.). Vedi la reference per untrusted input: https://securitylab.github.com/resources/github-actions-untrusted-input/
- Il quoting della shell dentro run: non è una difesa affidabile, perché linjection avviene nella fase di rendering del template. Gli attacker possono uscire dalle virgolette o injectare operatori tramite input costruito ad arte.
## Pattern vulnerabile → RCE sul runner
Workflow vulnerabile (triggerato quando qualcuno apre una nuova issue):
Workflow vulnerabile (attivato quando qualcuno apre una nuova issue):
```yaml
name: New Issue Created
on:
@@ -36,7 +36,7 @@ with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: new
```
Se un attacker apre un issue intitolato $(id), lo step renderizzato diventa:
Se un attacker apre un issue intitolato $(id), il passo renderizzato diventa:
```sh
echo "New issue $(id) created"
```
@@ -44,12 +44,48 @@ La command substitution esegue id sul runner. Esempio di output:
```
New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created
```
Perché l'uso delle virgolette non ti salva:
- Le espressioni vengono renderizzate per prime, poi viene eseguito lo script risultante. Se il valore non attendibile contiene $(...), `;`, `"`/`'`, o newlines, può alterare la struttura del programma nonostante le tue virgolette.
Perché le virgolette non ti salvano:
- Le expression vengono renderizzate prima, poi viene eseguito lo script risultante. Se il valore non fidato contiene $(...), `;`, `"`/`'`, o newlines, può alterare la struttura del programma nonostante le virgolette.
## Pattern sicuro (shell variables via env)
## Confusione dello stato dei commenti: spoofed bot comments → shell injection
Mitigazione corretta: copia l'input non attendibile in una variabile di ambiente, quindi usa l'espansione nativa della shell ($VAR) nello script di run. Non reinserire con ${{ ... }} all'interno del comando.
Una variante pericolosa appare quando un workflow **cerca i commenti e poi tratta il commento restituito come trusted automation state**. Per esempio, `peter-evans/find-comment` può cercare tramite `body-includes` ed esporre il `comment-body` corrispondente come output di uno step. Se il workflow **non** limita anche `comment-author`, qualsiasi utente che può commentare può spoofare il testo marker atteso da un bot.
```yaml
- uses: peter-evans/find-comment@v4
id: fc
with:
issue-number: ${{ github.event.issue.number }}
body-includes: "Opened a new issue in org/repo:"
```
Se quell'output viene successivamente incorporato nella sintassi shell, il workflow diventa sfruttabile anche se la sorgente originale era "solo un commento":
```yaml
- run: |
if [ '${{ steps.fc.outputs.comment-body }}' = '' ]; then
echo "new issue needed"
fi
```
Un attacker può pubblicare un commento che:
- corrisponde alla stringa marker cercata, e
- contiene contenuto capace di rompere la shell, come `' ]; <cmd>; if [ 'x`
Dopo che GitHub rende `${{ ... }}`, Bash riceve sintassi controllata dall'attaccante, non dati. Questo crea un exploit in **due fasi**:
1. **Provenance confusion**: il workflow scambia i commenti dell'attaccante per stato del bot.
2. **Script injection**: il `comment-body` restituito viene incollato in `run:` ed eseguito.
### TOCTOU race contro i commenti del bot
Se il commento legittimo del bot viene creato solo dopo qualche step precedente, un attacker può fare race pubblicando prima il commento spoofato. Se l'azione di ricerca restituisce il commento dell'attaccante prima che esista il vero commento del bot (o prima che venga selezionato), un commenter pubblico a basso privilegio può trasformare un workflow `issue_comment`/issue in esecuzione privilegiata sul runner.
### Pattern più sicuri per automazione guidata dai commenti
- Quando usi `find-comment`, richiedi **sia il contenuto che la provenance** (`comment-author`, identità repository/App, o un altro binding forte).
- Non usare i commenti come stato se un label, artifact, issue field, o datastore esterno può memorizzare lo stesso stato in modo più sicuro.
- Non incollare mai `comment-body`, i titoli delle issue, i labels, o qualsiasi output del workflow derivato da questi direttamente in `run:`.
- Se devi consumare il testo del commento, passalo tramite `env:` o un file e trattalo solo come dati.
## Safe pattern (shell variables via env)
Mitigazione corretta: copia l'input non fidato in una variabile d'ambiente, poi usa l'espansione nativa della shell ($VAR) nello script `run`. Non re-incorporare con ${{ ... }} dentro il comando.
```yaml
# safe
jobs:
@@ -62,32 +98,40 @@ TITLE: ${{ github.event.issue.title }}
run: |
echo "New issue $TITLE created"
```
Note:
- Evita di usare ${{ env.TITLE }} dentro run:. Questo reintroduce il rendering dei template nel comando e comporta lo stesso rischio di injection.
- Preferisci passare input non attendibili tramite la mappatura env: e riferirli con $VAR in run:.
Notes:
- Evita di usare ${{ env.TITLE }} dentro run:. Questo reintroduce il template rendering dentro il command e porta lo stesso injection risk.
- Preferisci passare input non fidati tramite mappatura env: e referenziarli con $VAR in run:.
## Superfici attivabili da utenti esterni (trattare come non attendibili)
## Reader-triggerable surfaces (considera come untrusted)
Account con solo permesso di lettura sui repository pubblici possono comunque innescare molti eventi. Qualsiasi campo nei contesti derivati da questi eventi deve essere considerato controllato dall'attaccante a meno che non sia dimostrato il contrario. Esempi:
Gli account con solo permesso di lettura sui repository pubblici possono comunque trigger many eventi. Qualsiasi campo nei contexts derivati da questi events deve essere considerato attacker-controlled a meno che non sia dimostrato il contrario. Esempi:
- issues, issue_comment
- discussion, discussion_comment (orgs can restrict discussions)
- discussion, discussion_comment (gli orgs possono limitare le discussions)
- pull_request, pull_request_review, pull_request_review_comment
- pull_request_target (pericoloso se usato in modo errato, viene eseguito nel contesto del repository base)
- fork (chiunque può forkare repository pubblici)
- watch (starring a repo)
- Indirettamente tramite catene workflow_run/workflow_call
- pull_request_target (pericoloso se usato male, esegue nel base repo context)
- fork (chiunque può fare fork di public repos)
- watch (starring di un repo)
- Indirectly via workflow_run/workflow_call chains
Quali campi specifici sono controllati dall'attaccante dipende dall'evento. Consulta la guida di GitHub Security Lab sugli input non attendibili: https://securitylab.github.com/resources/github-actions-untrusted-input/
Quali campi specifici siano attacker-controlled dipende dall'event. Consulta la guida di GitHub Security Lab sugli untrusted input: https://securitylab.github.com/resources/github-actions-untrusted-input/
## Suggerimenti pratici
## Validazione locale senza toccare il target repo
- Minimizza l'uso di espressioni all'interno di run:. Preferisci la mappatura env: + $VAR.
- Se devi trasformare l'input, fallo nella shell usando strumenti sicuri (printf %q, jq -r, ecc.), sempre partendo da una variabile di shell.
- Fai particolare attenzione quando interpoli nomi dei branch, titoli delle PR, username, label, titoli delle discussion e PR head refs in script, flag della riga di comando o percorsi di file.
- Per reusable workflows e composite actions, applica lo stesso schema: mappa su env e poi riferiscilo con $VAR.
Puoi riprodurre in sicurezza molti GitHub Actions script injections con [`act`](https://github.com/nektos/act): genera un synthetic event JSON, esegui il vulnerable workflow in locale e sostituisci l'output dell'external action con un valore controllato (per esempio un `comment-body` mockato). Questo è utile per debuggare la struttura del payload, verificare se il testo injectato lascia ancora una sintassi Bash valida e confermare un harmless canary exfiltration prima di qualsiasi live test.
## Riferimenti
## Consigli pratici
- Riduci al minimo l'uso di expressions dentro run:. Preferisci mappatura env: + $VAR.
- Se devi trasformare input, fallo nella shell usando strumenti sicuri (printf %q, jq -r, ecc.), partendo comunque da una shell variable.
- Fai particolare attenzione quando interpoli branch names, PR titles, usernames, labels, discussion titles e PR head refs dentro script, command-line flags o file paths.
- Per reusable workflows e composite actions, applica lo stesso pattern: mappa su env poi referenzia $VAR.
## References
- [Find Comment, Get Shell: Command Injection in dbts GitHub Actions](https://landh.tech/blog/20260701-find-comment-get-shell)
- [peter-evans/find-comment](https://github.com/peter-evans/find-comment)
- [GHSL-2023-109: GitHub Actions command injection in a TDesign Vue Next workflow](https://securitylab.github.com/advisories/GHSL-2023-109_TDesign_Vue_Next/)
- [nektos/act](https://github.com/nektos/act)
- [GitHub Actions: A Cloudy Day for Security - Part 1](https://binarysecurity.no/posts/2025/08/securing-gh-actions-part1)
- [GitHub workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions)
- [Contexts and expression syntax](https://docs.github.com/en/actions/learn-github-actions/contexts)