Skip to main content

IAM Customer-Managed Policies Should Not Allow Administrative Privileges

Overview

This check identifies customer-managed IAM policies that grant full administrative access through wildcard permissions (Action: "*" combined with Resource: "*"). Such policies give unrestricted access to every AWS service and resource in your account, violating the principle of least privilege.

Risk

Policies with *:* administrative privileges create severe security risks:

  • Complete account compromise - An attacker with these permissions can access, modify, or delete any resource
  • Data exfiltration - Unrestricted access to S3 buckets, databases, and secrets
  • Privilege escalation - Ability to create new admin users or modify existing permissions
  • Cover tracks - Permissions to disable CloudTrail, delete logs, and hide malicious activity
  • Financial impact - Launch expensive resources or access billing information

This is rated as High severity because it represents a foundational security weakness.

Remediation Steps

Prerequisites

  • AWS Console access with IAM permissions
  • Ability to modify IAM policies (iam:GetPolicy, iam:GetPolicyVersion, iam:CreatePolicyVersion, iam:DeletePolicyVersion)
  • Knowledge of what permissions the affected users/roles actually need

AWS Console Method

  1. Sign in to the AWS Management Console
  2. Navigate to IAM (Identity and Access Management)
  3. In the left navigation pane, click Policies
  4. Filter to show only Customer managed policies using the filter dropdown
  5. For each policy flagged by Prowler:
    • Click the policy name to open its details
    • Select the Permissions tab, then click JSON to view the policy document
    • Look for statements with "Action": "*" and "Resource": "*"
  6. Click Edit to modify the policy
  7. Replace the wildcard permissions with specific actions needed. For example:
    • Change "Action": "*" to specific actions like "Action": ["s3:GetObject", "s3:PutObject"]
    • Change "Resource": "*" to specific ARNs like "Resource": "arn:aws:s3:::my-bucket/*"
  8. Click Next and then Save changes

Tip: Use IAM Access Analyzer to generate a policy based on actual usage rather than guessing what permissions are needed.

AWS CLI (optional)

Step 1: Identify policies with administrative privileges

List all customer-managed policies:

aws iam list-policies \
--scope Local \
--query 'Policies[*].[PolicyName,Arn,DefaultVersionId]' \
--output table \
--region us-east-1

Step 2: Review a specific policy

Get the policy document for inspection:

aws iam get-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--version-id v1 \
--query 'PolicyVersion.Document' \
--output json \
--region us-east-1

Look for statements containing:

{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}

Step 3: Create a new, restricted policy version

Create a new version with least-privilege permissions:

aws iam create-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--policy-document file://restricted-policy.json \
--set-as-default \
--region us-east-1

Where restricted-policy.json contains your scoped-down permissions.

Step 4: Delete the old overly permissive version

aws iam delete-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--version-id v1 \
--region us-east-1

Note: You can have up to 5 policy versions. Delete old versions if needed.

CloudFormation (optional)

When defining IAM policies in CloudFormation, always use specific permissions instead of wildcards.

Bad example (triggers this check):

# DO NOT USE - This grants full administrative access
Resources:
BadAdminPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: '*'
Resource: '*'

Good example (least privilege):

AWSTemplateFormatVersion: '2010-09-09'
Description: Example of a properly scoped IAM policy

Resources:
S3ReadWritePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: S3BucketAccessPolicy
Description: Grants read/write access to a specific S3 bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowS3BucketAccess
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- arn:aws:s3:::my-application-bucket
- arn:aws:s3:::my-application-bucket/*
- Sid: AllowCloudWatchLogs
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:us-east-1:*:log-group:/aws/application/*

Deploy with:

aws cloudformation deploy \
--template-file iam-policy.yaml \
--stack-name scoped-iam-policy \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Terraform (optional)

When defining IAM policies in Terraform, always use specific permissions.

Bad example (triggers this check):

# DO NOT USE - This grants full administrative access
resource "aws_iam_policy" "bad_admin_policy" {
name = "bad-admin-policy"

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "*"
Resource = "*"
}]
})
}

Good example (least privilege):

resource "aws_iam_policy" "s3_access_policy" {
name = "s3-bucket-access-policy"
description = "Grants read/write access to a specific S3 bucket"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowS3BucketAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::my-application-bucket",
"arn:aws:s3:::my-application-bucket/*"
]
},
{
Sid = "AllowCloudWatchLogs"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:us-east-1:*:log-group:/aws/application/*"
}
]
})
}

Using data sources to scope permissions:

data "aws_caller_identity" "current" {}

resource "aws_iam_policy" "dynamodb_access" {
name = "dynamodb-table-access"

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query"
]
Resource = "arn:aws:dynamodb:us-east-1:${data.aws_caller_identity.current.account_id}:table/my-table"
}]
})
}

Verification

After modifying the policy, confirm it no longer grants administrative privileges:

  1. Go to IAM > Policies in the AWS Console
  2. Click on the modified policy name
  3. Review the JSON tab and confirm there are no "Action": "*" with "Resource": "*" combinations
  4. Re-run the Prowler check to verify remediation
CLI verification commands

Retrieve and inspect the current policy version:

# Get the policy details including default version
aws iam get-policy \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--region us-east-1

# Get the actual policy document
aws iam get-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--version-id <default-version-id> \
--query 'PolicyVersion.Document' \
--region us-east-1

Re-run the Prowler check:

prowler aws --checks iam_customer_attached_policy_no_administrative_privileges

Additional Resources

Notes

  • AWS-managed policies are excluded: This check only evaluates customer-created policies, not AWS-managed policies like AdministratorAccess
  • Use Access Analyzer: Before restricting permissions, use IAM Access Analyzer to generate policies based on actual CloudTrail activity - this prevents breaking applications
  • Permissions boundaries: Consider using permissions boundaries to limit the maximum permissions any identity can have
  • Test in non-production first: Before applying restricted policies in production, test them in a development environment
  • Gradual rollout: For critical systems, consider adding a deny policy first to log would-be denied actions before actually removing permissions
  • Emergency access: Maintain a separate, tightly controlled break-glass procedure for emergency administrative access rather than granting permanent admin permissions