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.

Scope All techniques here assume an attacker starting with valid AWS credentials — a compromised IAM user, a stolen session token, or a role assumed via SSRF/IMDS. None require physical or console access.

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:

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.