Cloud attack paths in AWS IAM: what a red teamer looks for first
Cloud does not fail at CVEs. It fails at identity. Here's the priority order a red teamer walks when they land in an AWS environment — which IAM graph edges matter, which don't, and what to test first when your assessor is time-boxed.
Nine of the last ten breaches we investigated in AWS environments were identity failures. Not CVEs. Not zero-days. Not container escapes. Someone had a sts:AssumeRole edge they didn’t understand, a service account with iam:PassRole on a resource its consumers could reach, or an OIDC federation trust with a wildcard subject condition. The paths were sitting in the graph the whole time; nobody had looked.
This post is the priority order a working red teamer uses when they’ve just landed in an AWS environment with limited time. If you’re scoping a cloud assessment, or building an internal capability, this is the first pass. See also our related walkthrough on Kubernetes hardening from an attacker’s viewpoint — most modern breaches chain both.
The mental model: IAM as a directed graph
Skip the “roles and policies” primer. The useful mental model is:
- Every principal (user, role, service, federated identity) is a node.
- Every statement that grants permission on another principal is an edge.
- Reachability from your foothold to a Target of Interest is the finding.
If you can describe an attack path in one sentence — “web app role can call sts:AssumeRole on the deploy role which has iam:PassRole to the admin role” — you have a finding. If you can’t describe it in one sentence, you probably don’t understand it yet.
There are open-source tools for this: PMapper, Cloudsplaining, Access Analyzer’s external-access findings, CloudFox, IAM Zero. All useful. None replace an operator reasoning about the graph.
The priority order
1. Assume-role edges you didn’t intend
sts:AssumeRole is the primary edge in the AWS IAM graph. Two questions:
- Which principals can assume which roles?
- Do the trust policy conditions actually constrain the caller?
The classic finding: a role’s trust policy allows any principal in the account ("Principal": {"AWS": "*"} or "arn:aws:iam::123:root") with no external condition. This means any credential leak, any developer with sts:AssumeRole on their user, any Lambda with a permissive policy — all of them can become this role. Query it with:
aws iam list-roles --query 'Roles[?AssumeRolePolicyDocument.Statement[?Principal.AWS==`*` || contains(Principal.AWS, `:root`)]].RoleName'
Then chase every hit. Most environments have three to five of these that were “temporary.”
2. iam:PassRole without resource constraints
If a principal can call a service (Lambda, EC2, ECS, CodeBuild, Glue) and pass an arbitrary role to it, they can execute code as that role. iam:PassRole with "Resource": "*" is the game-over version.
The engagement pattern: find a principal with lambda:CreateFunction + iam:PassRole:*. Create a Lambda that assumes the highest-privileged role in the account and invokes it. Fifteen minutes, one line of Terraform.
Constrain iam:PassRole by exact role ARN or path. Never *.
3. Wildcards on identity-adjacent actions
The “everything else” bucket. Any of these on * should be a paged-severity finding on discovery:
iam:*(obvious, still common in dev accounts)iam:AttachUserPolicy/iam:PutUserPolicy— direct self-escalationiam:AttachRolePolicy— sideways escalationiam:CreateAccessKeyon another useriam:CreatePolicyVersionon an existing policy with--set-as-defaultiam:UpdateAssumeRolePolicy— rewrite a role’s trust policysts:AssumeRoleWithSAML/sts:AssumeRoleWithWebIdentity— check the federation
Rhino Security’s classic “AWS IAM Privilege Escalation Methods” enumerated 21 of these in 2018. Most are still exploitable in 2026 environments where nobody rewrote the IAM as it grew.
4. Federation trust conditions
This is the current favourite. GitHub Actions OIDC integration is everywhere and misconfigured half the time. The archetype:
{
"Effect": "Allow",
"Principal": {"Federated": "arn:aws:iam::123:oidc-provider/token.actions.githubusercontent.com"},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/*"
}
}
}
That repo:my-org/* wildcard means any repository under my-org — including forks, including a new repo an attacker with commit access to one repo can create. The correct pattern pins to a specific repo and (ideally) a specific branch or environment:
"token.actions.githubusercontent.com:sub": "repo:my-org/prod-deploy:ref:refs/heads/main"
Same class of failure on GitLab CI, Bitbucket Pipelines, and Terraform Cloud. If your OIDC trust policies use StringLike with a wildcard, they’re almost certainly wrong.
5. Instance metadata service exposure
IMDSv1 is still out there. In 2026. In production. On EC2 instances behind SSRF-prone web apps. This is T1552.005 on the ATT&CK map — cloud instance metadata API abuse — and the standard chain is:
- SSRF in a web app that reaches
169.254.169.254 - Retrieve temporary credentials via IMDSv1
- Use them from the attacker’s machine (until rotation)
Fix: enforce IMDSv2 (session-token requirement) with HttpTokens=required. Enforce it at the account level via SCP so no launched instance can opt out:
{
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "*",
"Condition": {"StringNotEquals": {"ec2:MetadataHttpTokens": "required"}}
}
Then run the SCP against your account. Anything that fails to launch is telling you what still uses IMDSv1.
6. Cross-account confused deputy
An external role that trusts your account without a sts:ExternalId is a classic confused-deputy setup. If any principal in your account (a compromised Lambda, a leaked developer token) can assume that external role, the attacker gets the external account’s permissions.
Every sts:AssumeRole policy that trusts an external AWS account must include Condition: {"StringEquals": {"sts:ExternalId": "..."}} with a value only the intended caller knows. This is the tenth time we’ve had this conversation with a fintech in 2026.
7. S3 bucket policies vs IAM policies
Two policy layers, and they interact in ways that are non-obvious. Effective permissions are the union of IAM policies, bucket policies, ACLs, KMS key policies, and any resource-based policies. An IAM policy that appears restrictive can be widened by a bucket policy the IAM analyst never read.
Practical test: use aws-vault to assume the role you’re testing, then run aws s3 ls and try aws s3 cp against every bucket in the account. What actually works is your effective permission. This is more reliable than static policy analysis for finding the outliers.
8. Backup and snapshot exposure
ec2:CopySnapshot combined with cross-account snapshot sharing is the exfil path nobody talks about. Or rds:CopyDBSnapshot on a production database. Or ec2:CreateImage from a running instance you didn’t harden.
Query for snapshots shared outside the org, snapshots created recently by unexpected principals, and unencrypted snapshots. Any of those is either a finding or an incident.
Mapping to MITRE ATT&CK
Every AWS engagement maps to the Cloud matrix. The relevant tactics for the above:
- TA0007 Discovery →
T1069.003(Permission Groups),T1580(Infrastructure Discovery) - TA0006 Credential Access →
T1552.005(IMDS),T1552.001(files/env) - TA0004 Privilege Escalation →
T1078.004(Valid Accounts),T1548.005(Temporary Elevated Access misuse) - TA0009 Collection and TA0010 Exfiltration → snapshot copy, S3 bucket-policy abuse, cross-account share
Any finding worth shipping references the specific technique. Any technique reference should link to the graph edge that enabled it.
What defenders should build
Reading the above and thinking “how do I run this on my own account continuously” is the correct instinct.
- Access Analyzer for external access findings — free, native, always-on.
- PMapper or CloudFox run weekly, diffed against last week — surfaces new attack paths as they appear.
- SCPs at the org level for the highest-impact controls: IMDSv2-required, PassRole scoping, deny-wildcard-iam.
- CloudTrail management-events with alerting on the top-20 sensitive actions (
iam:UpdateAssumeRolePolicy,iam:CreateAccessKey,sts:AssumeRolefrom non-baseline principals). - Guardrails on federated identity trust — no
StringLikewith wildcards on OIDC subjects.
The pattern is the same as any other adversary-simulation program: know what the attacker looks at, look at it first, and detect the ones you can’t prevent. See our purple-team playbook if you want the whole operating model.
If your last cloud pentest was scoped as “review our AWS environment” without a clear IAM-graph deliverable — the outputs you got probably didn’t touch most of the paths above. That’s what a serious cloud red team engagement fixes.
Ready to test this in your own environment?
Scope an engagement and we'll bring the same rigor to your stack.
Scope an engagement