The IAM Permissions That Quietly Grant Admin, One Hop at a Time
Nobody attaches AdministratorAccess to a compromised access key and calls
it a breach report. Real IAM privilege escalation looks boring: a handful
of narrow, plausible-sounding permissions that individually pass a code
review, and together let a low-privilege identity mint itself a new one
with full account control. Security researcher Spencer Gietzen catalogued
the first broad list of these paths back in 2018, and the list has only
grown since — recent cloud-posture surveys still find escalation-capable
policy combinations in the large majority of scanned AWS accounts. The
mechanism is unglamorous and that’s exactly why it keeps working: no
exploit code, no CVE, just IAM doing precisely what its policy language
says.
Root cause
Every one of these paths reduces to the same shape: an identity can create
or modify a permission set, or hand an existing powerful role to a
resource it controls. iam:CreatePolicyVersion lets you rewrite the
default version of a policy already attached to you — so if you can
touch a policy at all, you can silently swap its contents for
"Action": "*" without ever calling AttachUserPolicy or
AttachRolePolicy, the actions most detections actually watch for.
iam:PassRole is the more common route: on its own it does nothing, but
paired with permission to launch a compute resource — lambda:CreateFunction
lambda:InvokeFunction,ec2:RunInstances,glue:CreateDevEndpoint,sagemaker:CreatePresignedNotebookInstanceUrl, orcloudformation:CreateStack— it lets you pass a role with far more privilege than your own to a service that will run code as that role on your behalf. CloudFormation is the sharpest version of this: a principal withcloudformation:CreateStackandiam:PassRolecan hand the stack an administrator role and have it provision anAdministratorAccess-attached IAM user as a stack resource, turning a single API call into full account takeover. None of these permissions look dangerous read in isolation on a policy diff; the danger is entirely in the combination, which is exactly what makes them survive manual review.
Blast radius
Because escalation here is pure policy manipulation rather than a memory corruption bug or a lateral-movement chain, there’s no patch window and no detection signature tied to a specific exploit — the “exploit” is a sequence of fully authorized, individually mundane API calls. That also means the blast radius scales with how the initial low-privilege credential was obtained, not with any skill on the attacker’s part: a leaked CI access key, an SSRF against the instance metadata service, or a phished developer session all land at the same starting line, and from there the privilege-escalation path is a checklist, not a research project. Documented incidents have gone from a stolen token to administrator-equivalent access in well under a day once the attacker finds one exploitable permission combination — and because the resulting admin role was created through ordinary IAM and CloudFormation calls, it often doesn’t stand out from routine infrastructure activity in a CloudTrail review unless someone is specifically looking for the pattern.
Remediation
Start by finding these paths before an attacker does: **AWS IAM Access
Analyzer**‘s policy-generation and unused-access findings won’t flag
escalation chains directly, but open-source tools built for exactly this —
Rhino Security Labs’ pacu privesc scanner and the aptly named
aws-privilege-escalation policy simulators — walk every principal’s
effective permissions against the known combination list and tell you
which identities are one API call from admin. Where you can, replace the
dangerous permissions outright: scope iam:PassRole with a
iam:PassedToService condition key so a role can only be passed to the
specific service that needs it, never to CloudFormation or Lambda
generically; and avoid granting iam:CreatePolicyVersion or
iam:SetDefaultPolicyVersion to anything short of your break-glass admin
role, since both let a principal rewrite its own effective permissions
without touching the attach/detach APIs that most alerting rules key on.
Permission boundaries are the structural fix — they cap the maximum
permissions any role or user created by a given principal can ever have,
so even a successful iam:CreateRole + iam:PassRole chain can’t produce
a role more powerful than the boundary allows, regardless of what policy
gets attached to it. At the organization level, an SCP denying
iam:CreatePolicyVersion, iam:PutRolePolicy, and iam:PutUserPolicy for
everything except a designated deployment role removes the whole
permission-authoring surface from ordinary accounts. And because none of
this looks anomalous in isolation, alert specifically on the sequence —
CloudTrail Lake or a SIEM rule that fires when a single principal calls
iam:PassRole followed within minutes by cloudformation:CreateStack,
lambda:CreateFunction, or ec2:RunInstances — since the individual
calls are routine but that pairing, from a principal that shouldn’t need
it, is the tell.
The uncomfortable finding underneath all of this is that least-privilege
IAM isn’t just about trimming unused permissions — it’s about knowing
which combinations of permissions are equivalent to *, because IAM’s
policy language has no concept of “these two grants together are more
dangerous than either alone.” A policy reviewer checking each statement in
isolation will pass every one of these chains. Only a tool, or a
reviewer, checking for the specific combinations will catch it.
Join the discussion
Comments for this post live on social — reply to the thread.
Related posts
Storm-0558: When a Stolen Signing Key Forges Any Token
A single stolen token-signing key let attackers mint valid cloud identities and read email in about 25 organizations. Root cause, blast radius, and what to do about your own keys.
CircleCI Breach: Stolen Session Cookies and Your CI Secrets
One infected engineer laptop, one stolen SSO session, and every customer secret in CircleCI was in play. What failed, the blast radius, and how to make CI secrets cheap to rotate.
The chalk/debug npm Compromise: Anatomy of a Package Hijack
One phished maintainer turned 18 ubiquitous npm packages into a crypto-stealing payload in about two hours. Here is how it worked and the build-pipeline controls that blunt it.