cloud security

Detecting AWS IAM Privilege Escalation

Detect AWS IAM privilege escalation paths using boto3 and Cloudsplaining policy analysis to identify overly permissive policies, dangerous permission combinations, and least-privilege violations

awsboto3cloudsplainingiamleast-privilegepolicy-analysisprivilege-escalation
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

This skill uses boto3 and Cloudsplaining-style analysis to identify IAM privilege escalation paths in AWS accounts. It downloads the account authorization details, analyzes each policy for dangerous permission combinations (iam:PassRole + lambda:CreateFunction, iam:CreatePolicyVersion, sts:AssumeRole), and flags policies that violate least-privilege principles.

When to Use

  • When investigating security incidents that require detecting aws iam privilege escalation
  • When building detection rules or threat hunting queries for this domain
  • When SOC analysts need structured procedures for this analysis type
  • When validating security monitoring coverage for related attack techniques

Prerequisites

  • Python 3.8+ with boto3 library
  • AWS credentials with IAM read-only access (iam:GetAccountAuthorizationDetails)
  • Optional: cloudsplaining Python package for HTML report generation

Steps

  1. Download IAM Authorization Details — Call iam:GetAccountAuthorizationDetails to retrieve all users, groups, roles, and policies
  2. Analyze Policies for Privilege Escalation — Check each policy for known escalation permission combinations
  3. Identify Wildcard Resource Policies — Flag policies using Resource: "*" with dangerous actions
  4. Map Principal-to-Policy Relationships — Build a graph of which principals can access which escalation paths
  5. Score and Prioritize Findings — Rank findings by severity based on escalation vector type
  6. Generate Report — Produce structured JSON report with remediation guidance

Expected Output

  • JSON report of privilege escalation findings with severity scores
  • List of dangerous permission combinations per principal
  • Wildcard resource policy audit results
  • Remediation recommendations for each finding
Source materials

References and resources

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

References 1

api-reference.md2.2 KB

AWS IAM Privilege Escalation Detection API Reference

boto3 IAM API

import boto3
 
iam = boto3.client("iam")
 
# Download full account authorization details
paginator = iam.get_paginator("get_account_authorization_details")
for page in paginator.paginate():
    users = page["UserDetailList"]
    roles = page["RoleDetailList"]
    policies = page["Policies"]
 
# Get specific policy version
policy = iam.get_policy_version(
    PolicyArn="arn:aws:iam::123456789012:policy/MyPolicy",
    VersionId="v2"
)

Cloudsplaining CLI

# Install
pip install cloudsplaining
 
# Download account authorization details
cloudsplaining download --profile myprofile
 
# Scan authorization file for privilege escalation
cloudsplaining scan --input-file default.json --output results/
 
# Scan a single policy file
cloudsplaining scan-policy-file --input-file policy.json

Known Privilege Escalation Vectors

Vector Required Permissions Risk
CreatePolicyVersion iam:CreatePolicyVersion Critical
AttachUserPolicy iam:AttachUserPolicy Critical
PutUserPolicy iam:PutUserPolicy Critical
PassRole + Lambda iam:PassRole, lambda:CreateFunction, lambda:InvokeFunction Critical
PassRole + EC2 iam:PassRole, ec2:RunInstances Critical
UpdateAssumeRolePolicy iam:UpdateAssumeRolePolicy Critical
PassRole + CloudFormation iam:PassRole, cloudformation:CreateStack High
PassRole + SSM iam:PassRole, ssm:SendCommand Critical

AWS CLI IAM Audit Commands

# List all users with attached policies
aws iam list-users --output json
 
# Get user's inline policies
aws iam list-user-policies --user-name admin
 
# Get attached managed policies
aws iam list-attached-user-policies --user-name admin
 
# Simulate policy evaluation
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/admin \
  --action-names iam:CreatePolicyVersion iam:AttachUserPolicy
 
# Get account authorization details (full dump)
aws iam get-account-authorization-details > auth-details.json

Parliament (Policy Linting)

pip install parliament
parliament --file policy.json

Scripts 1

agent.py9.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Detect AWS IAM privilege escalation paths using boto3 policy analysis."""

import json
import argparse
from datetime import datetime
from collections import defaultdict

try:
    import boto3
    HAS_BOTO3 = True
except ImportError:
    HAS_BOTO3 = False

ESCALATION_COMBOS = [
    {"name": "CreatePolicyVersion", "permissions": ["iam:CreatePolicyVersion"],
     "description": "Create new policy version with elevated privileges", "severity": "critical"},
    {"name": "SetDefaultPolicyVersion", "permissions": ["iam:SetDefaultPolicyVersion"],
     "description": "Set a previously created permissive policy version as default", "severity": "critical"},
    {"name": "PassRole+Lambda", "permissions": ["iam:PassRole", "lambda:CreateFunction", "lambda:InvokeFunction"],
     "description": "Pass privileged role to Lambda and invoke it", "severity": "critical"},
    {"name": "PassRole+EC2", "permissions": ["iam:PassRole", "ec2:RunInstances"],
     "description": "Launch EC2 with privileged instance profile", "severity": "critical"},
    {"name": "PassRole+CloudFormation", "permissions": ["iam:PassRole", "cloudformation:CreateStack"],
     "description": "Deploy CloudFormation stack with privileged role", "severity": "high"},
    {"name": "AttachUserPolicy", "permissions": ["iam:AttachUserPolicy"],
     "description": "Attach AdministratorAccess to own user", "severity": "critical"},
    {"name": "AttachGroupPolicy", "permissions": ["iam:AttachGroupPolicy"],
     "description": "Attach AdministratorAccess to own group", "severity": "critical"},
    {"name": "AttachRolePolicy", "permissions": ["iam:AttachRolePolicy"],
     "description": "Attach elevated policy to assumable role", "severity": "high"},
    {"name": "PutUserPolicy", "permissions": ["iam:PutUserPolicy"],
     "description": "Add inline policy to own user", "severity": "critical"},
    {"name": "PutGroupPolicy", "permissions": ["iam:PutGroupPolicy"],
     "description": "Add inline policy to own group", "severity": "high"},
    {"name": "AssumeRole", "permissions": ["sts:AssumeRole"],
     "description": "Assume role with higher privileges", "severity": "medium"},
    {"name": "PassRole+SageMaker", "permissions": ["iam:PassRole", "sagemaker:CreateNotebookInstance"],
     "description": "Create SageMaker notebook with privileged role", "severity": "high"},
    {"name": "UpdateAssumeRolePolicy", "permissions": ["iam:UpdateAssumeRolePolicy"],
     "description": "Modify trust policy to assume privileged role", "severity": "critical"},
    {"name": "PassRole+SSM", "permissions": ["iam:PassRole", "ssm:SendCommand"],
     "description": "Execute commands on EC2 via SSM with privileged role", "severity": "critical"},
]


def get_account_authorization(profile=None):
    """Download full IAM authorization details via boto3."""
    session = boto3.Session(profile_name=profile) if profile else boto3.Session()
    iam = session.client("iam")
    paginator = iam.get_paginator("get_account_authorization_details")
    details = {"UserDetailList": [], "GroupDetailList": [], "RoleDetailList": [], "Policies": []}
    for page in paginator.paginate():
        details["UserDetailList"].extend(page.get("UserDetailList", []))
        details["GroupDetailList"].extend(page.get("GroupDetailList", []))
        details["RoleDetailList"].extend(page.get("RoleDetailList", []))
        details["Policies"].extend(page.get("Policies", []))
    return details


def extract_policy_actions(policy_document):
    """Extract all actions from a policy document."""
    actions = set()
    if isinstance(policy_document, str):
        policy_document = json.loads(policy_document)
    for statement in policy_document.get("Statement", []):
        if statement.get("Effect") != "Allow":
            continue
        stmt_actions = statement.get("Action", [])
        if isinstance(stmt_actions, str):
            stmt_actions = [stmt_actions]
        for action in stmt_actions:
            actions.add(action.lower())
        resource = statement.get("Resource", "")
        if resource == "*" or (isinstance(resource, list) and "*" in resource):
            actions.add("__wildcard_resource__")
    return actions


def check_escalation_paths(actions):
    """Check if a set of actions contains known privilege escalation combos."""
    findings = []
    has_wildcard = "iam:*" in actions or "*" in actions
    for combo in ESCALATION_COMBOS:
        required = {p.lower() for p in combo["permissions"]}
        if has_wildcard or required.issubset(actions):
            findings.append({
                "escalation_path": combo["name"],
                "required_permissions": combo["permissions"],
                "description": combo["description"],
                "severity": combo["severity"],
            })
    return findings


def analyze_wildcard_policies(actions):
    """Flag dangerous wildcard action patterns."""
    dangerous_wildcards = []
    wildcard_patterns = [a for a in actions if a.endswith(":*") or a == "*"]
    dangerous_services = {"iam:*", "sts:*", "lambda:*", "ec2:*", "s3:*", "cloudformation:*", "*"}
    for wp in wildcard_patterns:
        if wp in dangerous_services:
            dangerous_wildcards.append({
                "action": wp, "severity": "critical" if wp in {"iam:*", "*"} else "high",
                "finding": f"Wildcard action {wp} grants broad access",
            })
    return dangerous_wildcards


def analyze_account(auth_details):
    """Analyze all principals for escalation paths."""
    findings = []
    for user in auth_details.get("UserDetailList", []):
        all_actions = set()
        for policy in user.get("UserPolicyList", []):
            all_actions.update(extract_policy_actions(policy.get("PolicyDocument", {})))
        for policy in user.get("AttachedManagedPolicies", []):
            arn = policy.get("PolicyArn", "")
            for p in auth_details.get("Policies", []):
                if p.get("Arn") == arn:
                    for ver in p.get("PolicyVersionList", []):
                        if ver.get("IsDefaultVersion"):
                            all_actions.update(extract_policy_actions(ver.get("Document", {})))
        escalations = check_escalation_paths(all_actions)
        wildcards = analyze_wildcard_policies(all_actions)
        if escalations or wildcards:
            findings.append({
                "principal_type": "User", "principal_name": user.get("UserName"),
                "arn": user.get("Arn"), "escalation_paths": escalations,
                "wildcard_findings": wildcards,
            })
    for role in auth_details.get("RoleDetailList", []):
        all_actions = set()
        for policy in role.get("RolePolicyList", []):
            all_actions.update(extract_policy_actions(policy.get("PolicyDocument", {})))
        for policy in role.get("AttachedManagedPolicies", []):
            arn = policy.get("PolicyArn", "")
            for p in auth_details.get("Policies", []):
                if p.get("Arn") == arn:
                    for ver in p.get("PolicyVersionList", []):
                        if ver.get("IsDefaultVersion"):
                            all_actions.update(extract_policy_actions(ver.get("Document", {})))
        escalations = check_escalation_paths(all_actions)
        wildcards = analyze_wildcard_policies(all_actions)
        if escalations or wildcards:
            findings.append({
                "principal_type": "Role", "principal_name": role.get("RoleName"),
                "arn": role.get("Arn"), "escalation_paths": escalations,
                "wildcard_findings": wildcards,
            })
    return findings


def generate_report(findings, source):
    """Generate privilege escalation analysis report."""
    severity_counts = defaultdict(int)
    for f in findings:
        for esc in f.get("escalation_paths", []):
            severity_counts[esc["severity"]] += 1
        for wc in f.get("wildcard_findings", []):
            severity_counts[wc["severity"]] += 1
    return {
        "report_time": datetime.utcnow().isoformat(),
        "source": source,
        "total_principals_with_findings": len(findings),
        "severity_summary": dict(severity_counts),
        "findings": findings,
    }


def main():
    parser = argparse.ArgumentParser(description="AWS IAM Privilege Escalation Detector")
    parser.add_argument("--profile", help="AWS CLI profile name")
    parser.add_argument("--input-file", help="Pre-downloaded authorization details JSON")
    parser.add_argument("--output", default="iam_escalation_report.json")
    args = parser.parse_args()

    if args.input_file:
        with open(args.input_file) as f:
            auth_details = json.load(f)
        source = args.input_file
    elif HAS_BOTO3:
        auth_details = get_account_authorization(args.profile)
        source = f"live-account (profile={args.profile or 'default'})"
    else:
        print("[!] boto3 not installed and no --input-file provided")
        return

    findings = analyze_account(auth_details)
    report = generate_report(findings, source)
    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Analyzed {len(auth_details.get('UserDetailList', []))} users, "
          f"{len(auth_details.get('RoleDetailList', []))} roles")
    print(f"[+] Found {len(findings)} principals with escalation paths")
    print(f"[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
Keep exploring