Skip to content
LP
Blog / Cloud Security

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.

LeetProtect Research · ·9 min read

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-escalation
  • iam:AttachRolePolicy — sideways escalation
  • iam:CreateAccessKey on another user
  • iam:CreatePolicyVersion on an existing policy with --set-as-default
  • iam:UpdateAssumeRolePolicy — rewrite a role’s trust policy
  • sts: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:

  1. SSRF in a web app that reaches 169.254.169.254
  2. Retrieve temporary credentials via IMDSv1
  3. 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 DiscoveryT1069.003 (Permission Groups), T1580 (Infrastructure Discovery)
  • TA0006 Credential AccessT1552.005 (IMDS), T1552.001 (files/env)
  • TA0004 Privilege EscalationT1078.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:AssumeRole from non-baseline principals).
  • Guardrails on federated identity trust — no StringLike with 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.

> MAPPING
MITRE ATT&CK
T1078.004: Valid Accounts (Cloud)T1550.001: Application Access TokenT1069.003: Cloud Permission GroupsT1552.005: Cloud Instance Metadata APIT1580: Cloud Infrastructure Discovery
TAGS
AWSIAMCloud SecurityPrivilege EscalationRed Team
[ ENGAGE ]

Ready to test this in your own environment?

Scope an engagement and we'll bring the same rigor to your stack.

Scope an engagement