AWS IAM Privilege Escalation: Attack Paths, Detection, and Least Privilege Enforcement
A technical breakdown of how IAM misconfigurations enable privilege escalation in AWS — covering iam:PassRole abuse, CloudTrail evasion techniques, GuardDuty detection, and the controls that actually prevent account compromise.
In on-premises environments, network segmentation — firewalls, VLANs, subnets — defines the security boundary. In AWS, the network is largely secondary. The real boundary is IAM. An attacker who obtains AWS credentials isn’t constrained by network topology; they’re constrained by what those credentials are permitted to do in the AWS API.
This makes IAM misconfiguration the primary attack surface in cloud environments. The gap between what a policy needs to allow and what it actually allows — often widened by wildcard permissions and deadline pressure — is where most AWS privilege escalation paths begin.
This article covers how those escalation paths work, how to detect them in CloudTrail, and what controls close them. The perspective is defensive: understanding the attack chain is how you build effective detection and prioritize remediation.
The Starting Point: Credential Exposure
The most common initial access vector in AWS is credential exposure — either through SSRF reaching the instance metadata service, through leaked keys in code repositories, or through compromised CI/CD pipeline secrets.
Instance Metadata Service (IMDS): EC2 instances with an attached IAM role expose temporary credentials at http://169.254.169.254. An SSRF vulnerability that can reach this endpoint gives an attacker the role’s credentials.
Modern AWS accounts should have IMDSv2 enforced. IMDSv2 requires a PUT request to obtain a session token before credentials can be retrieved — a standard SSRF via GET request won’t work:
# IMDSv2: requires session token firstTOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/" \ -H "X-aws-ec2-metadata-token: $TOKEN"IMDSv2 doesn’t eliminate SSRF-based credential theft — an attacker who controls the request headers can still make the PUT request — but it prevents the simplest exploitation pattern and blocks server-side redirects that the old IMDSv1 allowed. Enforce IMDSv2 at the account level via SCP or per-instance via the metadata options:
aws ec2 modify-instance-metadata-options \ --instance-id i-1234567890abcdef0 \ --http-tokens required \ --http-put-response-hop-limit 1The hop limit of 1 prevents the metadata service from being reachable from inside containers running on the EC2 instance — relevant for ECS and self-managed Kubernetes.
The iam:PassRole Escalation Path
Once an attacker has credentials, they enumerate what those credentials can do. The most consequential escalation combination in AWS is iam:PassRole paired with a service launch permission like ec2:RunInstances or lambda:CreateFunction.
iam:PassRole is a delegation permission — it doesn’t grant direct access to anything, but it allows attaching an existing IAM role to an AWS service. Combined with the ability to launch a compute resource, it allows an attacker to create infrastructure with a more privileged role than the one they currently have.
How the escalation works conceptually:
- Attacker has credentials with
iam:PassRole(Resource:*) andec2:RunInstances - Attacker enumerates existing roles:
aws iam list-roles - Attacker identifies a highly privileged role — e.g., one with
AdministratorAccess - Attacker launches an EC2 instance with that role attached via
--iam-instance-profile - The new instance boots with the privileged role’s temporary credentials accessible on its metadata service
The key point: the IAM API allows this because the credentials have iam:PassRole with a wildcard resource. AWS doesn’t check whether the role being passed is more privileged than the one being used — it only checks whether the permission to pass exists.
This is one of 21+ documented IAM privilege escalation paths catalogued by Rhino Security Labs. Other significant paths include:
iam:CreatePolicyVersion— create a new version of an existing policy with modified permissionsiam:AttachUserPolicy/iam:AttachRolePolicy— attach a managed policy (includingAdministratorAccess) to a principaliam:CreateAccessKey— create access keys for another user (if the current credentials can do this for admin users, the escalation is direct)lambda:CreateFunction+iam:PassRole+lambda:InvokeFunction— similar to the EC2 path but via Lambda
Detecting Escalation in CloudTrail
AWS CloudTrail logs every API call. Privilege escalation events have specific signatures. The following are the most important to alert on:
Detecting iam:PassRole events:
# CloudTrail Insights query (Athena / CloudTrail Lake)SELECT eventTime, userIdentity.principalId, userIdentity.arn, requestParameters, sourceIPAddress, userAgentFROM cloudtrail_logsWHERE eventName = 'PassRole' AND eventSource = 'iam.amazonaws.com'ORDER BY eventTime DESCLIMIT 100;Alert on PassRole events where the role being passed has AdministratorAccess or custom high-privilege policies. Cross-reference with whether the actor’s principal has ever performed this action before — first-time PassRole from a developer credential is anomalous.
Detecting new EC2 instances with privileged profiles:
SELECT eventTime, userIdentity.arn, requestParameters.iamInstanceProfile.arn, sourceIPAddress, awsRegionFROM cloudtrail_logsWHERE eventName = 'RunInstances' AND requestParameters.iamInstanceProfile.arn IS NOT NULLORDER BY eventTime DESC;Correlate the attached profile ARN against a list of high-privilege roles. An ec2:RunInstances event that references an admin role should trigger immediate investigation.
Detecting IAM policy modifications:
SELECT eventTime, userIdentity.arn, eventName, requestParametersFROM cloudtrail_logsWHERE eventName IN ( 'CreatePolicyVersion', 'AttachUserPolicy', 'AttachRolePolicy', 'AttachGroupPolicy', 'PutUserPolicy', 'PutRolePolicy')ORDER BY eventTime DESC;Any unexpected policy attachment or inline policy creation is a high-fidelity escalation signal.
CloudTrail Evasion and Detection
Adversaries who understand CloudTrail will attempt to work around it. The documented techniques and their detection:
Regional blind spots: CloudTrail must be configured as a multi-region trail to capture events in all regions. Attackers operating in unused regions (af-south-1, me-south-1, ap-east-1) evade single-region trails.
Detection: Enable a multi-region trail and alert on API activity in regions your organization doesn’t use. An RunInstances event in Cape Town from a principal that normally operates in us-east-1 is an immediate alert.
STS role chaining for attribution obfuscation: Attackers use sts:AssumeRole to chain through multiple roles, making the audit trail harder to follow. Each AssumeRole event uses a different session name, and the final API calls are attributed to the last role in the chain.
Detection: CloudTrail logs the full userIdentity context including sessionContext.sessionIssuer, which traces back to the original federated or IAM identity. Build queries that follow the chain:
SELECT eventTime, userIdentity.type, userIdentity.principalId, userIdentity.sessionContext.sessionIssuer.arn, eventName, awsRegionFROM cloudtrail_logsWHERE eventName = 'AssumeRole' AND userIdentity.type = 'AssumedRole'ORDER BY eventTime DESC;Response: When you see an unexpected AssumeRole chain, start from the most recent event and work backwards through principalId to find the original credential.
CloudTrail disruption:
# Attacker commandaws cloudtrail stop-logging --name production-trailThis generates a StopLogging event — but that event is recorded before logging stops. More importantly, sudden log volume drops are detectable at the SIEM layer.
Detection: Alert on cloudtrail:StopLogging, cloudtrail:DeleteTrail, and cloudtrail:UpdateTrail. Use a Service Control Policy to deny these actions from non-break-glass principals:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyCloudTrailModification", "Effect": "Deny", "Action": [ "cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "cloudtrail:UpdateTrail" ], "Resource": "*", "Condition": { "StringNotEquals": { "aws:PrincipalArn": [ "arn:aws:iam::123456789012:role/BreakGlassAdmin" ] } } } ]}GuardDuty: Automated Threat Detection
Manual CloudTrail querying is reactive. AWS GuardDuty provides automated detection for known threat patterns using ML baselines and threat intelligence.
Relevant GuardDuty finding types for IAM escalation:
IAMUser/AnomalousBehavior— API calls inconsistent with established baselineIAMUser/InstanceCredentialExfiltration.OutsideAWS— EC2 instance credentials used from an external IPCredentialAccess:IAMUser/AnomalousBehavior— unusual credential enumerationRecon:IAMUser/MaliciousIPCaller— API calls from known threat intelligence IPsPrivilegeEscalation:IAMUser/AnomalousBehavior— permission changes consistent with escalation patterns
GuardDuty should be enabled in all regions (it’s priced per API event volume and relatively inexpensive compared to the detection value). Route findings to Security Hub for centralized management and EventBridge for automated response.
Least Privilege: Fixing the Root Cause
Detection is important, but the root cause is over-permissive IAM policies. The iam:PassRole + ec2:RunInstances combination is dangerous because Resource: * on iam:PassRole means any role can be passed.
Constrain PassRole to specific roles:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::123456789012:role/WebServerRole", "Condition": { "StringEquals": { "iam:PassedToService": "ec2.amazonaws.com" } } } ]}The iam:PassedToService condition is critical — it restricts which service can receive the passed role, preventing reuse of the PassRole permission for Lambda, ECS, or other services.
IAM Permission Boundaries: A permission boundary is a managed policy attached to a role or user that defines the maximum permissions they can have — even if their own policies grant more. A developer with iam:CreateRole can’t create roles with permissions outside their own permission boundary:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "BoundaryForDeveloperRoles", "Effect": "Allow", "Action": [ "s3:*", "ec2:Describe*", "cloudwatch:*" ], "Resource": "*" } ]}Attach this as a boundary to developer-created roles, and those roles can never exceed the boundary — even if the policy attached to them grants AdministratorAccess.
Automated policy analysis:
AWS IAM Access Analyzer identifies resource policies and IAM roles that grant access to external principals or exceed intended scope. It generates findings for overly permissive policies that can be reviewed and remediated.
cloudsplaining (open source) generates an HTML report of IAM policies in an account, highlighting privilege escalation paths, resource wildcard usage, and data exfiltration risks:
pip install cloudsplainingcloudsplaining download --profile default --output-directory ./account-datacloudsplaining analyze --input-directory ./account-data --output-directory ./reportPacu (Rhino Security Labs) is the primary framework used by red teams to test AWS environments for exploitable privilege escalation paths. Running Pacu’s iam__privesc_scan against your own account (with appropriate authorization) identifies the same vectors an attacker would:
Pacu > import_keys --access-key-id AK... --secret-key ...Pacu > run iam__enum_permissionsPacu > run iam__privesc_scanThe output maps your current credentials’ escalation possibilities — what paths exist, what additional permissions are needed, and which are directly exploitable. Running this in a staging environment mirrors what an attacker with equivalent credentials would attempt.
Defense Checklist
The controls that address each stage of the attack path:
| Stage | Risk | Control |
|---|---|---|
| Credential exposure | IMDS SSRF | Enforce IMDSv2; hop limit = 1 for containers |
| Credential exposure | Leaked keys | Rotate regularly; use IAM Identity Center (SSO) over long-lived keys |
| Permission enumeration | iam:GetAccountAuthorizationDetails | Restrict this permission; use Access Analyzer instead |
| PassRole escalation | Wildcard resource on PassRole | Scope to specific role ARNs + PassedToService condition |
| Role creation abuse | iam:CreateRole without boundary | Require permission boundary on all developer-created roles (enforce via SCP) |
| CloudTrail evasion | Regional blind spots | Multi-region trail; SCP denying StopLogging/DeleteTrail |
| Detection | Unknown unknowns | GuardDuty enabled in all regions; findings to Security Hub |
| Assessment | Unknown exposure | Regular cloudsplaining / Access Analyzer review; Pacu in staging |
The common pattern across all of these: wildcard permissions granted for convenience create attack paths that aren’t obvious to developers but are immediately visible to an attacker doing basic enumeration. IAM least privilege is primarily an engineering culture and process problem — the technical controls exist, but they require deliberate effort to apply at the time a policy is written rather than after an incident.
Author
Varkin Academy