Cloud Security
AWS IAM
privilege escalation
cloud pentesting

AWS IAM Privilege Escalation: Attack Paths

AWS IAM privilege escalation paths: iam:PassRole abuse, policy versioning, sts:AssumeRole chains and how to detect them with CloudTrail.

SecraJuly 6, 202610 min read

Privilege escalation in AWS IAM is one of the most rewarding phases of any offensive cloud engagement. An attacker rarely lands directly in an account with administrator permissions. The usual pattern is to compromise a limited identity (an access key leaked in a repository, an EC2 instance role reached through SSRF, a service user with poorly scoped permissions) and from there hunt for the path that leads to AdministratorAccess. This article maps the most exploited AWS IAM privilege escalation paths, with the offensive command that makes each one real and, for every one of them, the defensive fix that cuts it off at the root.

The angle here is deliberately offensive. If you are after the conceptual foundation of identities and permissions, we have a dedicated guide on what IAM is and how identity and access management works. Here we assume that context and go straight to how it breaks.

Why IAM is the battleground of offensive cloud

In a classic datacenter, privilege escalation played out in the operating system: kernel exploits, SUID services, credentials in memory. In AWS the control plane has shifted to IAM. The permission, not the binary, is the primitive an attacker chains. A badly written policy turns a read-only user into an administrator without touching a single host.

The root cause is almost always the same: IAM management permissions granted to identities that should never hold them. Actions such as iam:CreatePolicyVersion, iam:AttachUserPolicy or iam:PassRole look harmless in a shallow review, yet each one enables a direct path to full control of the account. Rhino Security Labs documented more than twenty distinct IAM escalation methods, and most of them remain valid today because they depend on permission design, not on a provider bug.

Enumerating permissions: the prerequisite step

No escalation starts blind. The first thing to do is enumerate what the compromised identity can actually do.

The stealthy approach is to read the policy directly when there are permissions to do so:

aws iam get-user
aws iam list-attached-user-policies --user-name victim
aws iam list-user-policies --user-name victim
aws sts get-caller-identity

When there is no visibility over your own policies, the go-to tool is enumerate-iam (Andrés Riancho, NCC Group), which brute forces read-only calls to infer which actions are allowed without relying on iam:Get*. In a more complete operation, the Pacu framework from Rhino Security Labs automates the whole cycle: the iam__enum_permissions module builds the effective permission map and iam__privesc_scan automatically tests known escalation paths against that map. A typical Pacu flow:

run iam__enum_permissions
run iam__privesc_scan

The scan returns the viable paths ordered by ease of exploitation. From there, the work is manual and surgical.

AWS IAM privilege escalation paths

iam:PassRole and pivoting to compute services

iam:PassRole is probably the most dangerous IAM permission when it is granted without conditions. It does nothing on its own: it authorizes handing an existing role to an AWS service. The problem is that many compute services run code under the role you pass them.

If the identity has iam:PassRole over a privileged role plus lambda:CreateFunction and lambda:InvokeFunction, the attacker creates a Lambda function that assumes that role and runs commands with its permissions:

aws lambda create-function --function-name pwn \
  --runtime python3.12 --role arn:aws:iam::ACCT:role/privileged-role \
  --handler pwn.handler --zip-file fileb://pwn.zip
aws lambda invoke --function-name pwn out.json

The same primitive works with ec2:RunInstances (launching an instance with a privileged instance profile and reading its credentials from the IMDS), with glue:CreateDevEndpoint, with sagemaker:CreateNotebookInstance, with datapipeline or with cloudformation. We develop the serverless vector in depth in the guide to serverless pentesting on AWS Lambda.

Defensive fix: never grant iam:PassRole with Resource: "*". Restrict it to the exact ARN of the role the workload needs and add the iam:PassedToService condition to tie it to a single service (for example lambda.amazonaws.com). Combine it with organization-level SCPs that deny iam:PassRole over administrative roles.

Policy versioning: CreatePolicyVersion and SetDefaultPolicyVersion

IAM managed policies keep up to five versions. If the identity has iam:CreatePolicyVersion over a policy attached to it, it can create a new version with administrator permissions and mark it as the default in a single command:

aws iam create-policy-version --policy-arn arn:aws:iam::ACCT:policy/my-policy \
  --policy-document file://admin.json --set-as-default

Where admin.json grants Action: "*" over Resource: "*". The escalation is instant.

A quieter variant leverages iam:SetDefaultPolicyVersion: if an older, more permissive version already exists in the policy history, it is enough to roll back to it without creating anything new, which leaves less of a trail:

aws iam set-default-policy-version --policy-arn arn:aws:iam::ACCT:policy/my-policy --version-id v1

Defensive fix: treat iam:CreatePolicyVersion and iam:SetDefaultPolicyVersion as administrative permissions. Block them with an SCP except for a very tightly scoped identity administration role. Apply permission boundaries to operational identities: even if they manage to rewrite their policy, the boundary limits the effective permissions to the defined minimum.

AttachUserPolicy and PutUserPolicy: direct self-escalation

If the identity can modify its own policies, the escalation is trivial. With iam:AttachUserPolicy you attach the managed AdministratorAccess policy to the user itself:

aws iam attach-user-policy --user-name victim \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

With iam:PutUserPolicy you inject an inline policy with full permissions without depending on any existing managed policy. The group variants (iam:AttachGroupPolicy, iam:PutGroupPolicy, iam:AddUserToGroup) and the role variants (iam:AttachRolePolicy, iam:PutRolePolicy) achieve the same thing through lateral paths. Other classic self-escalations: iam:CreateAccessKey over a privileged user, or iam:CreateLoginProfile / iam:UpdateLoginProfile to set a console password on an identity with more permissions.

Defensive fix: no operational identity should be able to edit IAM policies. These are tier 0 permissions. Split them into a dedicated administration role, protect it with MFA and permission boundaries, and enable IAM Access Analyzer in unused access mode to spot who accumulates management permissions they never use.

sts:AssumeRole chains and the confused deputy

sts:AssumeRole is legitimate and everywhere, but it becomes an escalation path when the trust policy of a privileged role is too open. A Principal with a wildcard, a missing condition or an ExternalId that is not validated all let a low-privilege identity assume a high-privilege role:

aws sts assume-role --role-arn arn:aws:iam::ACCT:role/admin-role \
  --role-session-name esc

Role chaining links several successive AssumeRole calls to hop between accounts in the organization until reaching the management account. And if the identity holds iam:UpdateAssumeRolePolicy over a privileged role, it can rewrite its trust policy to authorize itself and then assume it. These chains are a central objective in a cloud penetration test across AWS, Azure and GCP, where the value lies in chaining minor flaws all the way to a critical compromise.

Defensive fix: audit every trust policy looking for a Principal with wildcards or external accounts without an ExternalId. Restrict sts:AssumeRole with conditions (aws:PrincipalOrgID, sts:ExternalId, mandatory MFA) and treat iam:UpdateAssumeRolePolicy as a tier 0 permission.

Reproducing it in a lab with CloudGoat

None of these paths should be tried for the first time in production. CloudGoat, the vulnerable-by-design environment from Rhino Security Labs, deploys reproducible scenarios with Terraform. Two of them are the reference for practicing IAM escalation:

  • iam_privesc_by_rollback: reproduces the escalation via iam:SetDefaultPolicyVersion by rolling back to an old and permissive version of a policy.
  • iam_privesc_by_attachment: reproduces the abuse of an EC2 instance role to which the attacker attaches the administrator policy by leveraging iam:PassRole and the instance profile.

The practice cycle is straightforward:

./cloudgoat.py create iam_privesc_by_rollback

CloudGoat hands over the starting credentials and the objective. Practicing both scenarios with Pacu and the CLI teaches you to recognize the same patterns in a real environment and, above all, which events each action generates in CloudTrail.

Defense: closing every path systematically

The targeted fixes from each section consolidate into four layers that, combined, close the escalation surface:

  1. Real least privilege. No workload identity should hold IAM management permissions. Review iam:*, PassRole without a condition and wildcards in Action or Resource. It is the same criterion we apply when cataloguing the most dangerous cloud misconfiguration errors in AWS and Azure.
  2. Permission boundaries. They act as an impassable ceiling: even if an identity manages to attach AdministratorAccess to itself, the boundary limits the effective permissions. They are the specific countermeasure against self-escalation.
  3. Service Control Policies (SCPs). At the AWS Organizations level, they globally deny dangerous actions (iam:CreatePolicyVersion, iam:SetDefaultPolicyVersion, iam:UpdateAssumeRolePolicy) except for explicit administration roles. No account policy can override them.
  4. Detection in CloudTrail. Every path leaves an identifiable event: CreatePolicyVersion, SetDefaultPolicyVersion, AttachUserPolicy, PutUserPolicy, CreateAccessKey, UpdateAssumeRolePolicy and anomalous AssumeRole calls. Configure alerts on these events (through EventBridge, GuardDuty or CloudTrail Lake and Athena queries) and correlate them with the origin (an unusual IP assuming an admin role is a high-fidelity signal).

This defensive posture is reinforced by continuously measuring configuration with the tools we describe in what CSPM is and cloud security posture management, and by extending the same effective-permissions principle to the SaaS plane with SSPM. The definitive validation, however, is offensive: only a real exercise confirms which paths are genuinely exploitable. At Secra we verify it with our cloud audit service.

Frequently asked questions

What is the most dangerous IAM permission for privilege escalation?

iam:PassRole without conditions, combined with creation permissions in a compute service (lambda:CreateFunction, ec2:RunInstances, glue:CreateDevEndpoint). It lets you run code under the identity of a privileged role without touching its policies. Close behind come iam:CreatePolicyVersion and iam:AttachUserPolicy, which enable direct self-escalation to administrator.

Does a permission boundary stop IAM escalation?

Yes, it is one of the most effective countermeasures. The permission boundary defines the ceiling of effective permissions for an identity. Even if an attacker manages to attach the AdministratorAccess policy, the boundary limits what they can actually do to the set defined in it. It is the specific defense against AttachUserPolicy, PutUserPolicy and CreatePolicyVersion.

How do I detect a privilege escalation attempt in CloudTrail?

Configure alerts on IAM management events: CreatePolicyVersion, SetDefaultPolicyVersion, AttachUserPolicy, PutUserPolicy, UpdateAssumeRolePolicy, CreateAccessKey and AssumeRole from unusual origins. GuardDuty generates anomaly findings on these actions, and CloudTrail Lake or Athena let you hunt them retroactively.

Which tools are used to test IAM escalation paths?

For enumeration, enumerate-iam infers permissions without relying on iam:Get*. Pacu automates the permission map (iam__enum_permissions) and the path scan (iam__privesc_scan). To reproduce scenarios in a safe lab, CloudGoat deploys vulnerable environments with Terraform, such as iam_privesc_by_rollback and iam_privesc_by_attachment.

Do SCPs replace least privilege?

No, they complement it. SCPs are an organization-wide upper limit that no account can exceed, ideal for globally blocking dangerous actions. But they do not grant permissions nor replace well-designed policies. A robust defense combines SCPs (organizational ceiling), permission boundaries (per-identity ceiling) and least-privilege policies (specific permissions).

Conclusion

Privilege escalation in AWS IAM does not depend on sophisticated exploits, but on management permissions granted to whoever should not hold them. The paths are known and reproducible: abuse of iam:PassRole, policy versioning, self-escalation with AttachUserPolicy or PutUserPolicy, and sts:AssumeRole chains. The good news is that each one has a concrete fix, and the four defensive layers (least privilege, permission boundaries, SCPs and detection in CloudTrail) close them systematically. If you run workloads on AWS and have not validated your IAM escalation surface with an offensive exercise, get in touch with our team for an assessment.

About the author

Secra Solutions team

Ethical hackers with OSCP, OSEP, OSWE, CRTO, CRTL and CARTE certifications, 7+ years of experience in offensive cybersecurity, and authors of CVE-2025-40652 and CVE-2023-3512.

Share article