IAM is where most AWS compromises either stall or succeed. An attacker who lands in an AWS environment with a low-privilege set of credentials — a developer's exposed access key, an SSRF that reaches the EC2 metadata service, a Lambda function with a misconfigured execution role — needs a path to something useful. In most environments, that path exists. Teams understand "don't give users AdministratorAccess" but miss the indirect escalation paths that are just as dangerous. This post is a field guide to the ones I find most consistently overlooked.
iam:PassRole + Compute Service Abuse
This is the most common escalation path I encounter, and the most underestimated. The iam:PassRole permission allows a principal to pass an IAM role to an AWS service. On its own, it sounds benign — but combined with the ability to create or modify certain compute resources, it's a full privilege escalation.
The pattern: an attacker with iam:PassRole and lambda:CreateFunction (or lambda:UpdateFunctionCode) can create a Lambda function, assign it any role they're allowed to pass — including roles with far greater privilege — and invoke that function. The function executes with the permissions of the passed role, not the attacker's original principal.
# Create a Lambda that calls sts:GetCallerIdentity to confirm privilege
# Then replaces with a function that does the actual damage
# Step 1: Create escalation function (attacker's code)
aws lambda create-function \
--function-name escalation-demo \
--runtime python3.12 \
--role arn:aws:iam::123456789012:role/HighPrivilegeRole \
--handler index.handler \
--zip-file fileb://payload.zip
# Step 2: Invoke it — now running as HighPrivilegeRole
aws lambda invoke \
--function-name escalation-demo \
--payload '{}' output.json
The services that support iam:PassRole-based escalation go well beyond Lambda: EC2 instance profiles, Glue jobs, ECS task definitions, CodeBuild projects, SageMaker training jobs. Each one is a different entry point to the same escalation. Enumerate your iam:PassRole grants carefully — broad permission to pass roles is almost always unintentional.
Detection
# CloudTrail Athena query — iam:PassRole followed by compute creation
SELECT
userIdentity.arn AS principal,
eventTime,
eventName,
requestParameters,
sourceIPAddress
FROM cloudtrail_logs
WHERE eventName IN ('PassRole', 'CreateFunction', 'UpdateFunctionCode',
'CreateJob', 'RunInstances', 'CreateTrainingJob')
AND eventTime >= current_timestamp - interval '1' hour
ORDER BY principal, eventTime ASC
iam:CreateAccessKey on Existing Users
The permission iam:CreateAccessKey is often granted to administrators for break-glass scenarios or onboarding automation. What it really means: anyone who holds this permission can create additional access keys for any IAM user — including users with greater privilege than themselves.
An attacker with iam:CreateAccessKey on a low-privilege account simply enumerates IAM users (iam:ListUsers is commonly available), identifies one with broad permissions, and creates a new key pair under that user. The original user's legitimate keys are unaffected. Nothing gets rotated, nothing breaks. The compromise may not be detected until a key audit months later.
# Enumerate users, find a privileged target, create new key
aws iam list-users --query 'Users[*].[UserName,UserId]' --output table
# Create access key for the target user
aws iam create-access-key --user-name target-admin-user
# Returns: AccessKeyId, SecretAccessKey — new credentials ready to use
Detection
Alert on CreateAccessKey events where requestParameters.userName differs from userIdentity.userName. A user creating an access key for themselves is normal. A user creating an access key for a different user is almost never legitimate outside of a defined automation context.
# CloudTrail Athena query — cross-user key creation
SELECT
userIdentity.userName AS actor,
requestParameters.userName AS target_user,
eventTime,
sourceIPAddress,
responseElements.accessKey.accessKeyId AS new_key_id
FROM cloudtrail_logs
WHERE eventName = 'CreateAccessKey'
AND userIdentity.userName != requestParameters.userName
ORDER BY eventTime DESC
EC2 Instance Metadata Service (IMDS) Abuse
The EC2 Instance Metadata Service (IMDS) is accessible from within any EC2 instance at 169.254.169.254. If an instance has an IAM role attached, credentials for that role are available via IMDS without authentication — any process running on the instance can retrieve them. This becomes a privilege escalation vector when two conditions are met: an attacker gains code execution on an EC2 instance (via SSRF, RCE in a web app, compromised dependency, etc.) and the attached IAM role has significant permissions.
# From inside an EC2 instance (or via SSRF to 169.254.169.254)
# Retrieve the attached role name
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Retrieve the actual credentials
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyEC2Role
# Returns: AccessKeyId, SecretAccessKey, Token, Expiration
The IMDSv2 requirement (requiring a session token acquired via PUT request) significantly mitigates pure SSRF attacks against IMDS, but doesn't help when an attacker has actual code execution on the instance. Treat any EC2 instance that runs untrusted code — container workloads, user-supplied scripts, build agents — as a potential credential compromise vector and scope the instance role accordingly.
The overprivileged build agent problem
The most dangerous IMDS exposure pattern I see consistently: CI/CD build agents (CodeBuild, self-hosted GitHub Actions runners on EC2, Jenkins) with broad IAM roles because they "need to deploy everything." A compromised build — via a malicious dependency, a poisoned cache, or a supply chain attack — can exfiltrate those credentials before anyone notices. Least-privilege here is not optional.
CloudFormation Stack Privilege Escalation
CloudFormation deserves its own escalation category. The permission cloudformation:CreateStack combined with iam:PassRole allows an attacker to create a stack that executes arbitrary resource creation under any role they can pass. If they can also call cloudformation:CreateChangeSet and cloudformation:ExecuteChangeSet, they can do the same against existing stacks.
More subtle: cloudformation:UpdateStack on a stack that deploys Lambda functions means an attacker can modify the template to inject new Lambda code, which then executes with the stack's service role. They don't need lambda:UpdateFunctionCode directly — CloudFormation does it for them.
Remediation Patterns
A few structural patterns that eliminate entire categories of these escalations:
- Enforce IMDSv2 everywhere. Set
HttpTokens: requiredon all EC2 instances via an AWS Config rule with auto-remediation. This doesn't stop code execution-based IMDS reads, but it eliminates the SSRF attack surface entirely. - Scope
iam:PassRolewith conditions. Use theiam:PassedToServicecondition key to restrict which services a principal can pass roles to."iam:PassedToService": "lambda.amazonaws.com"prevents passing to EC2, Glue, and everything else. - Separate deployment roles from developer roles. Developers shouldn't have
iam:CreateAccessKeyon arbitrary users oriam:PassRoleto high-privilege roles. Deployment automation should use separate, purpose-scoped roles that aren't accessible to developers via their daily credentials. - Audit with IAM Access Analyzer. Access Analyzer's unused access findings identify permissions that exist but haven't been used in the last 90 days — prime candidates for removal. Run it regularly and treat its findings as a backlog, not suggestions.
Automated Detection with CloudTrail + Athena
The single best investment for IAM threat detection is a CloudTrail → S3 → Athena pipeline. It's cheap, it's always-on, and it lets you write the SQL queries above against months of history. Complement it with EventBridge rules for real-time alerting on the high-signal events:
# EventBridge rule pattern — high-risk IAM events
{
"source": ["aws.iam"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventName": [
"CreateAccessKey",
"AttachUserPolicy",
"AttachRolePolicy",
"PutUserPolicy",
"PutRolePolicy",
"CreatePolicyVersion",
"SetDefaultPolicyVersion",
"AddUserToGroup"
]
}
}
Route that EventBridge rule to an SNS topic and into your SIEM. Every IAM mutation becomes an alert-eligible event. The noise will be high at first — tune it down with known-good automation ARNs and expected service accounts. What remains is almost always worth investigating.