90%
Of cloud security incidents involve over-privileged IAM identities: excess permissions amplify the blast radius of any compromise
90 days
CloudTrail activity window IAM Access Analyzer analyzes to generate least-privilege policy recommendations
AdministratorAccess
AWS managed policy most commonly found attached to service roles that should have narrow permissions: a frequent finding in IAM audits
Free
Cost of IAM Access Analyzer for policy generation within an AWS account: Access Analyzer findings are charged, but policy generation is free

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

IAM over-privilege is the cloud security equivalent of giving every employee a master key. When an attacker compromises a service account with AdministratorAccess instead of a narrow read-only policy, they get the entire AWS account. When a developer workstation has an IAM user with s3:* instead of s3:GetObject on specific buckets, a phishing attack becomes a data exfiltration incident.

The fix: least-privilege permissions: is straightforward in concept but tedious in practice. AWS provides tooling to make it mechanical.

Step 1: Generate the IAM Credential Report

The IAM credential report lists all IAM users in the account with metadata about their credentials: last access time, access key age, MFA status, and password last used. This is the starting point for finding unused credentials.

# Generate a fresh credential report
aws iam generate-credential-report

# Download the report (CSV format)
aws iam get-credential-report \
  --query 'Content' \
  --output text | base64 -d > credential-report.csv

# Parse it for high-value findings
# Find users with access keys not used in 90+ days
awk -F',' '
  NR > 1 && $11 != "N/A" && $11 != "no_information" {
    cmd = "date -d " $11 " +%s"
    cmd | getline key_ts
    close(cmd)
    now = systime()
    days = (now - key_ts) / 86400
    if (days > 90) print $1, "- access key not used in", int(days), "days"
  }
' credential-report.csv

# Find users with no MFA
awk -F',' 'NR > 1 && $8 == "false" {print $1, "- no MFA"}' credential-report.csv

# Find users with console access who have never logged in
awk -F',' 'NR > 1 && $5 == "true" && $6 == "no_information" {print $1, "- console enabled, never logged in"}' credential-report.csv

Step 2: Find Unused Permissions with IAM Access Advisor

IAM Access Advisor shows the last time each service was accessed by a principal. Any service not accessed in 90 days is a candidate for permission removal.

# Generate a service last-accessed report for a specific IAM user
aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::123456789012:user/myuser

# Retrieve the report
aws iam get-service-last-accessed-details \
  --job-id <job-id-from-above> \
  --query 'ServicesLastAccessed[?LastAuthenticated==null || LastAuthenticated<=`2026-03-26`].ServiceName'

# For IAM roles: same commands work
aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::123456789012:role/MyAppRole

# List all roles and check their last-used timestamps
aws iam list-roles \
  --query 'Roles[*].{Name:RoleName, LastUsed:RoleLastUsed.LastUsedDate}' \
  --output table | sort -k4

IAM roles with no LastUsedDate in 90+ days: Consider whether the role is still needed. Unused roles are an attack surface: if they can be assumed, they give attackers their permissions without ever being detected in last-used audits.

Script to list all roles unused for 90+ days:

#!/bin/bash
CUTOFF=$(date -d '90 days ago' --iso-8601=seconds)

aws iam list-roles --query 'Roles[*].{Arn:Arn,Name:RoleName,LastUsed:RoleLastUsed.LastUsedDate}' \
  --output json | \
  jq --arg cutoff "$CUTOFF" '
    .[] | select(.LastUsed == null or .LastUsed < $cutoff) | 
    {Name, LastUsed: (.LastUsed // "never")}
  '
Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Step 3: Generate Least-Privilege Policies with IAM Access Analyzer

IAM Access Analyzer can analyze 90 days of CloudTrail logs for a role or user and generate a policy that grants only the permissions actually used during that period.

# Start policy generation for a role (uses CloudTrail data from the last 90 days)
aws accessanalyzer start-policy-generation \
  --policy-generation-details '{"principalArn":"arn:aws:iam::123456789012:role/MyAppRole"}' \
  --cloud-trail-details '{
    "accessRole": "arn:aws:iam::123456789012:role/AccessAnalyzerCloudTrailRole",
    "trailArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/MyTrail",
    "startTime": "2026-03-27T00:00:00Z",
    "endTime": "2026-06-26T00:00:00Z"
  }'

# Wait a few minutes, then retrieve the generated policy
aws accessanalyzer get-generated-policy \
  --job-id <job-id-from-above> \
  --query 'GeneratedPolicyResult.GeneratedPolicies[0].Policy'

The output is a JSON policy document containing only the actions this principal actually used during the analysis period. Apply it as a permission boundary or replace the existing overly-broad policy.

AWS Console alternative: IAM > Users/Roles > [select principal] > Access Advisor tab > Generate policy button

Important caveat: The generated policy covers only actions observed in the analysis window. If a role has permissions for disaster recovery procedures run once a year, those will not appear in a 90-day analysis. Review the generated policy for known infrequent operations before applying it.

Step 4: Remediate Common Over-Privilege Patterns

Pattern 1: AdministratorAccess on service roles

# Find all roles with AdministratorAccess attached
aws iam list-entities-for-policy \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
  --entity-filter Role \
  --query 'PolicyRoles[*].RoleName'

# For each, check what services it actually calls (via Access Advisor)
# Replace AdministratorAccess with a narrow policy derived from Access Analyzer

# Detach the overly broad policy
aws iam detach-role-policy \
  --role-name MyAppRole \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# Attach the generated least-privilege policy
aws iam put-role-policy \
  --role-name MyAppRole \
  --policy-name MyAppRole-least-privilege \
  --policy-document file://generated-policy.json

Pattern 2: Wildcard S3 permissions

// Over-privileged (before)
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

// Least privilege (after): specific bucket and object actions
{
  "Effect": "Allow",
  "Action": [
    "s3:GetObject",
    "s3:PutObject"
  ],
  "Resource": [
    "arn:aws:s3:::my-app-bucket",
    "arn:aws:s3:::my-app-bucket/*"
  ]
}

Pattern 3: Stale IAM users for applications (should be roles)

# List all IAM users with active access keys
aws iam list-users | jq -r '.Users[].UserName' | while read user; do
  keys=$(aws iam list-access-keys --user-name "$user" \
    --query 'AccessKeyMetadata[?Status==`Active`].AccessKeyId' \
    --output text)
  if [ -n "$keys" ]; then
    echo "$user: $keys"
  fi
done

# Any application using an IAM user key should migrate to an IAM role
# EC2 → instance profile, Lambda → execution role, ECS → task role
# Migrate, then deactivate and eventually delete the IAM user
aws iam update-access-key \
  --user-name old-app-user \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --status Inactive

Pattern 4: Permission boundaries not enforced Permission boundaries cap the maximum permissions a principal can use: even if they are granted broader permissions by a policy, the boundary is the actual ceiling:

# Apply a permission boundary to limit blast radius
aws iam put-role-permissions-boundary \
  --role-name MyAppRole \
  --permissions-boundary arn:aws:iam::123456789012:policy/ProductionBoundary

The bottom line

AWS IAM over-privilege audits start with the credential report (find unused keys, no-MFA users, inactive accounts), then use IAM Access Advisor to identify services not accessed in 90 days, then use IAM Access Analyzer policy generation to produce a least-privilege replacement policy. The most common finding is AdministratorAccess on service roles that only need a handful of specific API actions. Migrate application IAM users to IAM roles wherever possible, and apply permission boundaries to cap the blast radius of any future over-granting.

Frequently asked questions

How do you audit AWS IAM permissions for least privilege?

Three steps: download the IAM credential report to find unused access keys and inactive users; use IAM Access Advisor (IAM > user/role > Access Advisor tab) to see which AWS services have not been used in 90 days; then use IAM Access Analyzer policy generation to analyze 90 days of CloudTrail logs and generate a policy containing only the permissions actually exercised. Remove AdministratorAccess and wildcard policies, replace with generated narrow policies.

How do you find over-privileged IAM roles in AWS?

Run 'aws iam list-entities-for-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --entity-filter Role' to find all roles with AdministratorAccess. Run 'aws iam list-roles --query Roles[*].RoleLastUsed.LastUsedDate' to find roles unused for 90+ days. For each role, generate a service last-accessed report using IAM Access Advisor to see which services were actually called, then remove permissions for services not used.

What is AWS IAM least privilege and how do I enforce it?

Least privilege means granting only the permissions required to perform a specific task — no more. In AWS IAM, enforce it by: starting with no permissions (deny-by-default) and adding only what is needed; using IAM Access Analyzer to generate least-privilege policies from CloudTrail activity (it shows exactly which actions an identity used in the last 90 days); setting permission boundaries on IAM roles to cap maximum permissions regardless of attached policies; and enabling AWS Config rule 'iam-policy-no-statements-with-admin-access' to continuously monitor for overly permissive policies. The most dangerous over-privilege patterns: iam:* (full IAM control), s3:* (all S3 operations), and * with NotResource conditions.

What is AWS IAM Access Analyzer and how do I use it?

IAM Access Analyzer identifies resource policies that grant access to external principals (other AWS accounts, public access, or federated identities outside your organization). Enable it per-region and per-account, or at the AWS Organization level. It continuously analyzes S3 bucket policies, IAM role trust policies, KMS key policies, Lambda resource policies, and SQS queue policies, and generates findings for any that allow access to external principals. For least-privilege policy generation: use the 'Generate policy based on CloudTrail activity' feature in IAM Access Analyzer to create a policy for a specific IAM role that includes only the actions that role actually used.

How do I audit and remove unused AWS IAM permissions?

A four-step process: (1) Enable CloudTrail across all regions and accounts if not already done — IAM Access Advisor and policy generation both rely on CloudTrail events. (2) Use IAM Access Advisor (visible per-user or per-role in the IAM console under 'Access Advisor' tab) to see last-accessed dates per AWS service — permissions for services not accessed in 90+ days are candidates for removal. (3) Use open-source tools PMapper or Cloudsplaining to map privilege escalation paths and identify high-risk permission combinations (iam:PassRole + ec2:RunInstances, iam:CreatePolicyVersion, etc.). (4) Remove unused policies incrementally and test each change in non-production before production.

What is IAM privilege escalation in AWS and which permission combinations create escalation paths?

IAM privilege escalation occurs when a principal uses their existing lower-privilege permissions to grant themselves or another identity additional AWS permissions, effectively bypassing the intended access boundary. The most dangerous escalation paths come from specific permission combinations rather than individually dangerous permissions. The combination of iam:PassRole and ec2:RunInstances allows an attacker to launch an EC2 instance with a higher-privileged IAM role attached and then access that instance's credentials via the metadata service. iam:CreatePolicyVersion allows creating a new version of an existing IAM policy with any permissions, including AdministratorAccess, and setting it as the default. iam:AttachRolePolicy combined with iam:PassRole allows attaching any policy to any role the attacker can assume. sts:AssumeRole with overly permissive trust policies allows assuming roles with broader permissions. The tool PMapper (Principal Mapper) analyzes your AWS account's IAM configuration and automatically maps these escalation paths, showing which principals can escalate to which other principals and the steps required. Run PMapper as part of your IAM audit to identify privilege escalation risks that standard policy review misses: the dangerous combinations are often spread across multiple policies and not obvious from reviewing individual policies in isolation.

Sources & references

  1. AWS IAM Access Analyzer Documentation
  2. AWS: Refining Permissions with Access Advisor
  3. AWS: Generating Least Privilege Policies

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.