Advanced

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:

Terminal window
# IMDSv2: requires session token first
TOKEN=$(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:

Terminal window
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1

The 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:

  1. Attacker has credentials with iam:PassRole (Resource: *) and ec2:RunInstances
  2. Attacker enumerates existing roles: aws iam list-roles
  3. Attacker identifies a highly privileged role — e.g., one with AdministratorAccess
  4. Attacker launches an EC2 instance with that role attached via --iam-instance-profile
  5. 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 permissions
  • iam:AttachUserPolicy / iam:AttachRolePolicy — attach a managed policy (including AdministratorAccess) to a principal
  • iam: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:

Terminal window
# CloudTrail Insights query (Athena / CloudTrail Lake)
SELECT
eventTime,
userIdentity.principalId,
userIdentity.arn,
requestParameters,
sourceIPAddress,
userAgent
FROM cloudtrail_logs
WHERE eventName = 'PassRole'
AND eventSource = 'iam.amazonaws.com'
ORDER BY eventTime DESC
LIMIT 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:

Terminal window
SELECT
eventTime,
userIdentity.arn,
requestParameters.iamInstanceProfile.arn,
sourceIPAddress,
awsRegion
FROM cloudtrail_logs
WHERE eventName = 'RunInstances'
AND requestParameters.iamInstanceProfile.arn IS NOT NULL
ORDER 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:

Terminal window
SELECT eventTime, userIdentity.arn, eventName, requestParameters
FROM cloudtrail_logs
WHERE 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:

Terminal window
SELECT
eventTime,
userIdentity.type,
userIdentity.principalId,
userIdentity.sessionContext.sessionIssuer.arn,
eventName,
awsRegion
FROM cloudtrail_logs
WHERE 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:

Terminal window
# Attacker command
aws cloudtrail stop-logging --name production-trail

This 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 baseline
  • IAMUser/InstanceCredentialExfiltration.OutsideAWS — EC2 instance credentials used from an external IP
  • CredentialAccess:IAMUser/AnomalousBehavior — unusual credential enumeration
  • Recon:IAMUser/MaliciousIPCaller — API calls from known threat intelligence IPs
  • PrivilegeEscalation: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:

Terminal window
pip install cloudsplaining
cloudsplaining download --profile default --output-directory ./account-data
cloudsplaining analyze --input-directory ./account-data --output-directory ./report

Pacu (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_permissions
Pacu > run iam__privesc_scan

The 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:

StageRiskControl
Credential exposureIMDS SSRFEnforce IMDSv2; hop limit = 1 for containers
Credential exposureLeaked keysRotate regularly; use IAM Identity Center (SSO) over long-lived keys
Permission enumerationiam:GetAccountAuthorizationDetailsRestrict this permission; use Access Analyzer instead
PassRole escalationWildcard resource on PassRoleScope to specific role ARNs + PassedToService condition
Role creation abuseiam:CreateRole without boundaryRequire permission boundary on all developer-created roles (enforce via SCP)
CloudTrail evasionRegional blind spotsMulti-region trail; SCP denying StopLogging/DeleteTrail
DetectionUnknown unknownsGuardDuty enabled in all regions; findings to Security Hub
AssessmentUnknown exposureRegular 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.

V

Author

Varkin Academy

Tags

#aws #cloud security #IAM #privilege escalation #CloudTrail #GuardDuty #cyber security