cloud security

Performing AWS Privilege Escalation Assessment

Performing authorized privilege escalation assessments in AWS environments to identify IAM misconfigurations that allow users or roles to elevate their permissions using Pacu, CloudFox, Principal Mapper, and manual IAM policy analysis techniques.

awscloud-securityiamoffensive-securitypacuprivilege-escalation
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When conducting authorized penetration testing of AWS IAM configurations
  • When validating that IAM policies follow the principle of least privilege
  • When assessing the blast radius of a compromised AWS credential
  • When building security reviews for IAM role and policy changes in CI/CD pipelines
  • When evaluating cross-account trust relationships for privilege escalation risks

Do not use for unauthorized testing against AWS accounts, for assessing non-IAM attack vectors (SSRF, application vulnerabilities), or as a substitute for comprehensive cloud penetration testing. Always obtain written authorization before testing.

Prerequisites

  • Written authorization for privilege escalation testing in the target AWS account
  • Test IAM user or role with limited permissions as the starting point
  • Pacu installed (pip install pacu)
  • CloudFox installed (go install github.com/BishopFox/cloudfox@latest)
  • PMapper (Principal Mapper) installed (pip install principalmapper)
  • AWS CLI configured with test credentials and CloudTrail logging enabled for audit trail

Workflow

Step 1: Enumerate Starting Permissions

Establish the baseline permissions of the test principal before attempting escalation.

# Get current identity
aws sts get-caller-identity
 
# Enumerate inline and attached policies for the current user
aws iam list-user-policies --user-name test-user
aws iam list-attached-user-policies --user-name test-user
 
# Get group memberships and group policies
aws iam list-groups-for-user --user-name test-user
for group in $(aws iam list-groups-for-user --user-name test-user --query 'Groups[*].GroupName' --output text); do
  echo "=== Group: $group ==="
  aws iam list-group-policies --group-name "$group"
  aws iam list-attached-group-policies --group-name "$group"
done
 
# Simulate specific API calls to map effective permissions
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::ACCOUNT:user/test-user \
  --action-names iam:CreateUser iam:AttachUserPolicy iam:PassRole \
    lambda:CreateFunction ec2:RunInstances sts:AssumeRole \
  --query 'EvaluationResults[*].[EvalActionName,EvalDecision]' --output table

Step 2: Scan for Privilege Escalation Paths with Pacu

Use Pacu's privilege escalation scanner to identify known IAM escalation techniques.

# Start Pacu session
pacu
 
# Create session and set credentials
Pacu (new:session) > set_keys --key-alias privesc-test
 
# Enumerate IAM configuration
Pacu > run iam__enum_users_roles_policies_groups
Pacu > run iam__enum_permissions
 
# Run privilege escalation scanner
Pacu > run iam__privesc_scan
 
# The scanner checks for 21+ known escalation methods including:
# - iam:CreatePolicyVersion (create admin policy version)
# - iam:SetDefaultPolicyVersion (revert to permissive older version)
# - iam:AttachUserPolicy / iam:AttachRolePolicy (attach admin policy)
# - iam:PutUserPolicy / iam:PutRolePolicy (create inline admin policy)
# - iam:PassRole + lambda:CreateFunction (Lambda with admin role)
# - iam:PassRole + ec2:RunInstances (EC2 with admin instance profile)
# - iam:CreateLoginProfile / iam:UpdateLoginProfile (set console password)
# - iam:CreateAccessKey (create keys for other users)
# - sts:AssumeRole (assume more privileged roles)
# - glue:CreateDevEndpoint + iam:PassRole (Glue with admin role)

Step 3: Map Privilege Escalation Graphs with PMapper

Use Principal Mapper to build a graph of all IAM principals and identify escalation edges.

# Collect IAM data for graph construction
pmapper graph create --account ACCOUNT_ID
 
# Query for paths to admin
pmapper query 'who can do iam:AttachUserPolicy with * on *'
pmapper query 'who can do sts:AssumeRole with arn:aws:iam::ACCOUNT:role/AdminRole'
 
# Find all principals that can escalate to admin
pmapper analysis
 
# Visualize the privilege escalation graph
pmapper visualize --filetype png
 
# Check specific escalation paths
pmapper query 'can arn:aws:iam::ACCOUNT:user/test-user do iam:CreatePolicyVersion with *'
pmapper query 'can arn:aws:iam::ACCOUNT:user/test-user do sts:AssumeRole with arn:aws:iam::ACCOUNT:role/*'

Step 4: Test Cross-Account Role Assumption

Evaluate cross-account trust policies for misconfigured role assumptions that allow unauthorized escalation.

# List all roles and their trust policies
aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output text | while read name arn; do
  trust=$(aws iam get-role --role-name "$name" --query 'Role.AssumeRolePolicyDocument' --output json 2>/dev/null)
  # Check for wildcards or broad trust
  echo "$trust" | python3 -c "
import json, sys
doc = json.load(sys.stdin)
for stmt in doc.get('Statement', []):
    principal = stmt.get('Principal', {})
    condition = stmt.get('Condition', {})
    if isinstance(principal, dict):
        aws_princ = principal.get('AWS', '')
    else:
        aws_princ = principal
    if '*' in str(aws_princ) or 'root' in str(aws_princ):
        has_external_id = 'sts:ExternalId' in str(condition)
        has_mfa = 'aws:MultiFactorAuthPresent' in str(condition)
        print(f'ROLE: $name')
        print(f'  Principal: {aws_princ}')
        print(f'  ExternalId required: {has_external_id}')
        print(f'  MFA required: {has_mfa}')
        if not has_external_id and not has_mfa:
            print(f'  WARNING: No ExternalId or MFA condition - confused deputy risk')
" 2>/dev/null
done
 
# Test role assumption
aws sts assume-role \
  --role-arn arn:aws:iam::TARGET_ACCOUNT:role/CrossAccountRole \
  --role-session-name privesc-test \
  --duration-seconds 900

Step 5: Enumerate CloudFox Attack Paths

Use CloudFox to identify additional attack surfaces including resource-based policies and service-specific escalation paths.

# Run all CloudFox checks
cloudfox aws --profile target-account all-checks -o ./cloudfox-output/
 
# Specific privilege escalation checks
cloudfox aws --profile target-account permissions
cloudfox aws --profile target-account role-trusts
cloudfox aws --profile target-account access-keys
cloudfox aws --profile target-account env-vars  # Lambda environment variables with secrets
cloudfox aws --profile target-account instances  # EC2 with instance profiles
cloudfox aws --profile target-account endpoints  # Exposed services

Step 6: Document Findings and Remediation

Compile all discovered escalation paths with proof-of-concept steps and remediation recommendations.

# Generate a consolidated report
cat > privesc-report.md << 'EOF'
# AWS Privilege Escalation Assessment Report
 
## Tested Escalation Vectors
 
| Vector | Status | Starting Principal | Escalated To | Risk |
|--------|--------|--------------------|--------------|------|
| iam:CreatePolicyVersion | EXPLOITABLE | test-user | AdministratorAccess | Critical |
| iam:PassRole + lambda:CreateFunction | EXPLOITABLE | dev-role | LambdaAdminRole | Critical |
| sts:AssumeRole (cross-account) | EXPLOITABLE | test-user | ProdAdminRole | High |
| iam:AttachUserPolicy | BLOCKED | test-user | N/A | N/A |
| ec2:RunInstances + iam:PassRole | BLOCKED | test-user | N/A | N/A |
 
## Remediation
1. Apply permission boundaries to all IAM users and roles
2. Remove iam:CreatePolicyVersion from non-admin principals
3. Add sts:ExternalId condition to all cross-account role trust policies
4. Implement SCP guardrails preventing privilege escalation actions
EOF

Key Concepts

Term Definition
IAM Privilege Escalation Exploiting overly permissive IAM policies to gain higher-level access than originally granted to a principal
Permission Boundary IAM policy that sets the maximum permissions a principal can have, regardless of identity-based policies attached to it
iam:PassRole IAM action allowing a principal to pass an IAM role to an AWS service, enabling the service to act with that role's permissions
Confused Deputy Attack where an attacker tricks a trusted service into performing actions on their behalf using cross-account role assumption without external ID validation
Service Control Policy AWS Organizations policy that sets maximum permissions for member accounts, providing guardrails against privilege escalation
Principal Mapper Open-source tool that models IAM principals and their escalation paths as a directed graph for analysis

Tools & Systems

  • Pacu: AWS exploitation framework with 21+ privilege escalation modules for automated detection and exploitation
  • Principal Mapper: Graph-based IAM analysis tool that maps escalation paths between principals
  • CloudFox: AWS enumeration tool focused on identifying attack paths from an attacker's perspective
  • IAM Policy Simulator: AWS-native tool for testing effective permissions against specific API actions
  • AWS Access Analyzer: Service that identifies resource policies granting external access and validates IAM policy changes

Common Scenarios

Scenario: Developer Role with iam:CreatePolicyVersion Leads to Admin Access

Context: During an authorized assessment, a tester discovers that a developer role has the iam:CreatePolicyVersion permission, which allows creating a new version of any customer-managed policy with arbitrary permissions.

Approach:

  1. Enumerate policies attached to the developer role using iam__enum_permissions in Pacu
  2. Identify that the role can call iam:CreatePolicyVersion on its own attached policy
  3. Create a new policy version with "Action": "*", "Resource": "*", "Effect": "Allow"
  4. Set the new version as the default policy version
  5. Verify admin access by calling iam:ListUsers, s3:ListBuckets, etc.
  6. Document the escalation chain and recommend removing iam:CreatePolicyVersion and implementing permission boundaries

Pitfalls: AWS limits managed policies to 5 versions. If all 5 exist, you must delete a version before creating a new one. Always record the original default version to restore it during cleanup. Permission boundaries prevent this escalation if properly configured, so verify boundary policies before declaring a finding.

Output Format

AWS Privilege Escalation Assessment Report
=============================================
Account: 123456789012 (Production)
Assessment Date: 2026-02-23
Starting Principal: arn:aws:iam::123456789012:user/test-user
Starting Permissions: S3 read-only, Lambda invoke, EC2 describe
Authorization: Signed by CISO, engagement #PT-2026-014
 
ESCALATION PATHS DISCOVERED: 4
 
[PRIVESC-001] iam:CreatePolicyVersion -> Admin
  Severity: CRITICAL
  Starting Permission: iam:CreatePolicyVersion on policy/dev-policy
  Escalation: Created policy version 6 with Action:* Resource:*
  Time to Exploit: < 2 minutes
  Remediation: Remove iam:CreatePolicyVersion, apply permission boundary
 
[PRIVESC-002] iam:PassRole + lambda:CreateFunction -> LambdaAdminRole
  Severity: CRITICAL
  Starting Permission: iam:PassRole, lambda:CreateFunction
  Escalation: Created Lambda function with AdminRole, invoked to get admin credentials
  Time to Exploit: < 5 minutes
  Remediation: Restrict iam:PassRole to specific role ARNs with condition key
 
[PRIVESC-003] sts:AssumeRole -> Cross-Account Admin
  Severity: HIGH
  Starting Permission: sts:AssumeRole on arn:aws:iam::987654321098:role/SharedRole
  Escalation: Role trust policy allows any principal in source account
  Remediation: Add sts:ExternalId condition and restrict Principal to specific roles
 
TOTAL ESCALATION PATHS: 4 (2 Critical, 1 High, 1 Medium)
PERMISSION BOUNDARIES IN PLACE: 0 / 47 IAM principals
SCP GUARDRAILS BLOCKING ESCALATION: 0 / 3 tested vectors
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.6 KB

API Reference: Performing AWS Privilege Escalation Assessment

AWS IAM API (boto3)

Method Description
iam.list_users() Enumerate all IAM users
iam.list_attached_user_policies(UserName) List managed policies attached to user
iam.list_user_policies(UserName) List inline policies on a user
iam.get_policy_version(PolicyArn, VersionId) Get policy document for analysis
iam.list_roles() Enumerate all IAM roles
iam.list_attached_role_policies(RoleName) List managed policies on a role
iam.list_groups_for_user(UserName) List group memberships for a user
iam.simulate_principal_policy(PolicySourceArn, ActionNames) Test permissions

AWS STS API

Method Description
sts.get_caller_identity() Identify current principal (user/role/account)
sts.assume_role(RoleArn, RoleSessionName) Assume a role for privilege escalation test

Pacu Modules (CLI)

Module Description
iam__enum_users_roles_policies_groups Full IAM enumeration
iam__privesc_scan Scan for 21+ privilege escalation vectors
iam__backdoor_users_keys Test access key creation ability
lambda__backdoor_new_roles Test Lambda-based escalation

Key Libraries

  • boto3 (pip install boto3): AWS SDK for IAM, STS, and service enumeration
  • pacu (pip install pacu): AWS exploitation framework (CLI-based)
  • pmapper (Principal Mapper): Graph-based IAM privilege analysis
  • cloudfox: Cloud penetration testing tool for AWS enumeration

Dangerous IAM Actions

Action Escalation Vector
iam:CreatePolicyVersion Create new policy version with admin permissions
iam:AttachUserPolicy Attach AdministratorAccess to self
iam:PassRole + lambda:CreateFunction Create Lambda with privileged role
iam:PutUserPolicy Add inline admin policy to self
sts:AssumeRole Assume more-privileged role
iam:UpdateAssumeRolePolicy Modify role trust to allow self-assumption

Configuration

Variable Description
AWS_PROFILE AWS CLI profile with test credentials
AWS_DEFAULT_REGION Default AWS region for API calls

References

Scripts 1

agent.py7.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
AWS Privilege Escalation Assessment Agent — AUTHORIZED TESTING ONLY
Assesses AWS IAM configurations for privilege escalation paths using boto3
and enumerates dangerous policy combinations.

WARNING: Only use with explicit written authorization on approved AWS accounts.
"""

import json
from datetime import datetime, timezone

import boto3
from botocore.exceptions import ClientError


def get_caller_identity() -> dict:
    """Get current AWS identity information."""
    sts = boto3.client("sts")
    identity = sts.get_caller_identity()
    return {
        "account": identity["Account"],
        "arn": identity["Arn"],
        "user_id": identity["UserId"],
    }


def enumerate_iam_users() -> list[dict]:
    """Enumerate all IAM users and their attached policies."""
    iam = boto3.client("iam")
    users = []
    paginator = iam.get_paginator("list_users")

    for page in paginator.paginate():
        for user in page["Users"]:
            username = user["UserName"]
            attached = iam.list_attached_user_policies(UserName=username)
            inline = iam.list_user_policies(UserName=username)
            groups = iam.list_groups_for_user(UserName=username)

            users.append({
                "username": username,
                "arn": user["Arn"],
                "attached_policies": [p["PolicyArn"] for p in attached["AttachedPolicies"]],
                "inline_policies": inline["PolicyNames"],
                "groups": [g["GroupName"] for g in groups["Groups"]],
                "has_console": user.get("PasswordLastUsed") is not None,
            })

    return users


def enumerate_iam_roles() -> list[dict]:
    """Enumerate IAM roles and their trust policies."""
    iam = boto3.client("iam")
    roles = []
    paginator = iam.get_paginator("list_roles")

    for page in paginator.paginate():
        for role in page["Roles"]:
            trust = role.get("AssumeRolePolicyDocument", {})
            attached = iam.list_attached_role_policies(RoleName=role["RoleName"])

            roles.append({
                "role_name": role["RoleName"],
                "arn": role["Arn"],
                "trust_policy": trust,
                "attached_policies": [p["PolicyArn"] for p in attached["AttachedPolicies"]],
            })

    return roles


def check_dangerous_permissions(users: list[dict]) -> list[dict]:
    """Identify users with dangerous permission combinations for privilege escalation."""
    iam = boto3.client("iam")
    escalation_paths = []

    dangerous_actions = [
        "iam:CreatePolicyVersion", "iam:SetDefaultPolicyVersion",
        "iam:PassRole", "iam:CreateRole", "iam:AttachUserPolicy",
        "iam:AttachRolePolicy", "iam:PutUserPolicy", "iam:PutRolePolicy",
        "iam:AddUserToGroup", "iam:UpdateAssumeRolePolicy",
        "sts:AssumeRole", "lambda:CreateFunction", "lambda:InvokeFunction",
        "lambda:UpdateFunctionCode", "ec2:RunInstances",
        "cloudformation:CreateStack", "glue:CreateDevEndpoint",
        "datapipeline:CreatePipeline", "ssm:SendCommand",
    ]

    for user in users:
        user_dangerous = []
        for policy_arn in user["attached_policies"]:
            try:
                policy = iam.get_policy(PolicyArn=policy_arn)
                version_id = policy["Policy"]["DefaultVersionId"]
                version = iam.get_policy_version(PolicyArn=policy_arn, VersionId=version_id)
                statements = version["PolicyVersion"]["Document"].get("Statement", [])

                for stmt in statements:
                    if stmt.get("Effect") != "Allow":
                        continue
                    actions = stmt.get("Action", [])
                    if isinstance(actions, str):
                        actions = [actions]
                    for action in actions:
                        if action == "*" or action in dangerous_actions:
                            user_dangerous.append({
                                "policy": policy_arn,
                                "action": action,
                                "resource": stmt.get("Resource", "*"),
                            })
            except ClientError:
                continue

        if user_dangerous:
            escalation_paths.append({
                "username": user["username"],
                "dangerous_permissions": user_dangerous,
                "escalation_risk": "HIGH" if any(
                    d["action"] == "*" for d in user_dangerous
                ) else "MEDIUM",
            })

    return escalation_paths


def check_role_chaining(roles: list[dict], account_id: str) -> list[dict]:
    """Identify role chaining opportunities for privilege escalation."""
    chains = []

    for role in roles:
        trust = role.get("trust_policy", {})
        for statement in trust.get("Statement", []):
            if statement.get("Effect") != "Allow":
                continue
            principal = statement.get("Principal", {})
            aws_principal = principal.get("AWS", [])
            if isinstance(aws_principal, str):
                aws_principal = [aws_principal]

            for p in aws_principal:
                if p == f"arn:aws:iam::{account_id}:root" or p == "*":
                    chains.append({
                        "role": role["role_name"],
                        "trust_principal": p,
                        "risk": "HIGH" if p == "*" else "MEDIUM",
                        "policies": role["attached_policies"],
                    })

    return chains


def generate_report(identity: dict, users: list, escalation: list, chains: list) -> str:
    """Generate privilege escalation assessment report."""
    lines = [
        "AWS PRIVILEGE ESCALATION ASSESSMENT — AUTHORIZED TESTING ONLY",
        "=" * 65,
        f"Account: {identity['account']}",
        f"Assessed As: {identity['arn']}",
        f"Date: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}",
        "",
        f"IAM Users Enumerated: {len(users)}",
        f"Escalation Paths Found: {len(escalation)}",
        f"Role Chaining Risks: {len(chains)}",
        "",
        "PRIVILEGE ESCALATION PATHS:",
        "-" * 40,
    ]
    for path in escalation:
        lines.append(f"  [{path['escalation_risk']}] {path['username']}")
        for perm in path["dangerous_permissions"][:5]:
            lines.append(f"    - {perm['action']} on {perm['resource']}")

    if chains:
        lines.extend(["", "ROLE CHAINING RISKS:", "-" * 40])
        for chain in chains:
            lines.append(f"  [{chain['risk']}] {chain['role']} trusts {chain['trust_principal']}")

    return "\n".join(lines)


if __name__ == "__main__":
    print("[!] AWS PRIVILEGE ESCALATION ASSESSMENT — AUTHORIZED TESTING ONLY\n")

    identity = get_caller_identity()
    print(f"[*] Account: {identity['account']}, Identity: {identity['arn']}")

    users = enumerate_iam_users()
    print(f"[*] Enumerated {len(users)} IAM users")

    roles = enumerate_iam_roles()
    print(f"[*] Enumerated {len(roles)} IAM roles")

    escalation = check_dangerous_permissions(users)
    chains = check_role_chaining(roles, identity["account"])

    report = generate_report(identity, users, escalation, chains)
    print(report)

    output = f"aws_privesc_{identity['account']}_{datetime.now(timezone.utc).strftime('%Y%m%d')}.json"
    with open(output, "w") as f:
        json.dump({"identity": identity, "users": users, "escalation_paths": escalation,
                    "role_chains": chains}, f, indent=2, default=str)
    print(f"\n[*] Results saved to {output}")
Keep exploring