OIDC Federation Abuse
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.
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.
{
"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:
What Makes It Abusable
Three trust-policy mistakes turn a legitimate federation into an assume-role path for an outsider:
Missing
audcondition. Without a check on:aud(GitHub usessts.amazonaws.com), any token from the provider is accepted regardless of who requested it.Wildcard in
sub.StringLikewithrepo: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
StringLikewhereStringEqualswas 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.
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.
{
"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:
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:
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.