Unattached Customer Managed IAM Policies Should Not Have Administrative Privileges
Overview
This check identifies unattached customer-managed IAM policies that grant unrestricted administrative access using *:* wildcards (all actions on all resources). Even though these policies are not currently attached to any users, groups, or roles, they represent a security risk because they could be attached accidentally or maliciously.
Risk
An unattached policy with full administrative privileges (*:*) creates several security concerns:
- Accidental attachment could instantly grant someone complete control over your AWS account
- Privilege escalation becomes trivial if an attacker can attach the policy to a compromised identity
- Confidentiality breach since full access allows reading all data across services
- Data destruction since full access allows deleting any resource
- Compliance violations as most security frameworks require least-privilege access
Think of it like leaving a master key lying around unused. It is not currently opening any doors, but if someone picks it up, they have access to everything.
Remediation Steps
Prerequisites
- Access to the AWS Console with IAM permissions
- Permission to view and delete IAM policies (
iam:ListPolicies,iam:GetPolicyVersion,iam:DeletePolicy)
AWS Console Method
- Sign in to the AWS Management Console
- Navigate to IAM (Identity and Access Management)
- In the left navigation pane, click Policies
- Click the Filter policies dropdown and select Customer managed
- For each policy flagged by Prowler:
- Click the policy name to open its details
- Go to the Permissions tab and click JSON to view the policy document
- Look for statements with
"Action": "*"and"Resource": "*"with"Effect": "Allow"
- Decide how to remediate:
- Delete the policy if it is no longer needed (recommended for unused policies)
- Modify the policy to use specific, scoped permissions instead of wildcards
To delete an unattached policy:
- Return to the Policies list
- Select the checkbox next to the policy
- Click the Actions dropdown and select Delete
- Confirm deletion
To modify a policy:
- On the policy details page, click Edit
- Replace
"*"with specific actions (e.g.,"s3:GetObject","ec2:DescribeInstances") - Replace
"*"in Resource with specific ARNs - Click Next, review changes, and click Save changes
AWS CLI (optional)
Find unattached customer-managed policies:
aws iam list-policies \
--scope Local \
--no-only-attached \
--query 'Policies[?AttachmentCount==`0`].[PolicyName,Arn,DefaultVersionId]' \
--output table \
--region us-east-1
View the policy document to check for *:* permissions:
aws iam get-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/<policy-name> \
--version-id v1 \
--query 'PolicyVersion.Document' \
--region us-east-1
Delete an unattached policy (after confirming it grants *:* and is not needed):
aws iam delete-policy \
--policy-arn arn:aws:iam::123456789012:policy/<policy-name>
Script to find all unattached policies with administrative privileges:
#!/bin/bash
# Find unattached customer-managed policies with *:* permissions
for policy_arn in $(aws iam list-policies --scope Local --no-only-attached \
--query 'Policies[?AttachmentCount==`0`].Arn' --output text); do
version_id=$(aws iam list-policy-versions --policy-arn "$policy_arn" \
--query 'Versions[?IsDefaultVersion].VersionId' --output text)
policy_doc=$(aws iam get-policy-version --policy-arn "$policy_arn" \
--version-id "$version_id" --query 'PolicyVersion.Document' --output json)
# Check for *:* pattern
if echo "$policy_doc" | grep -q '"Action".*"\*"' && \
echo "$policy_doc" | grep -q '"Resource".*"\*"'; then
echo "FLAGGED: $policy_arn"
fi
done
CloudFormation (optional)
CloudFormation does not directly help remediate this issue since the problem is existing policies that need to be deleted or modified. However, you can use CloudFormation to create properly scoped policies going forward.
Example of a well-scoped policy (instead of *:*):
AWSTemplateFormatVersion: '2010-09-09'
Description: Example of a properly scoped IAM policy
Resources:
ScopedS3Policy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: scoped-s3-read-policy
Description: Read-only access to specific S3 bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowS3ReadAccess
Effect: Allow
Action:
- s3:GetObject
- s3:ListBucket
Resource:
- arn:aws:s3:::my-specific-bucket
- arn:aws:s3:::my-specific-bucket/*
Tags:
- Key: Purpose
Value: LeastPrivilege
Key principles for CloudFormation IAM policies:
- Always specify explicit actions, never use
"*" - Always specify explicit resource ARNs, never use
"*" - Use conditions to further restrict access when possible
- Include a
Sid(statement ID) for clarity
Terraform (optional)
Like CloudFormation, Terraform is best used to create properly scoped policies going forward rather than remediating existing ones.
Example of a well-scoped policy:
resource "aws_iam_policy" "scoped_s3_policy" {
name = "scoped-s3-read-policy"
description = "Read-only access to specific S3 bucket"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowS3ReadAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::my-specific-bucket",
"arn:aws:s3:::my-specific-bucket/*"
]
}
]
})
tags = {
Purpose = "LeastPrivilege"
}
}
To import and manage existing policies with Terraform:
# Import existing policy (then modify to remove *:* permissions)
# Run: terraform import aws_iam_policy.existing_policy arn:aws:iam::123456789012:policy/MyPolicy
resource "aws_iam_policy" "existing_policy" {
name = "MyPolicy"
description = "Previously overpermissioned, now scoped"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
# Replace * with specific actions needed
"ec2:DescribeInstances",
"ec2:DescribeSecurityGroups"
]
Resource = "*" # EC2 describe actions require Resource: *
}
]
})
}
Verification
After remediating, verify the issue is resolved:
- Go to IAM > Policies in the AWS Console
- Filter to Customer managed policies
- For each previously flagged policy:
- Confirm it has been deleted, OR
- Click through to view the JSON and confirm
*:*permissions have been replaced with specific actions and resources
CLI verification commands
Re-run the check to find any remaining overpermissioned policies:
#!/bin/bash
# Verify no unattached policies have *:* permissions
found_issues=0
for policy_arn in $(aws iam list-policies --scope Local --no-only-attached \
--query 'Policies[?AttachmentCount==`0`].Arn' --output text --region us-east-1); do
version_id=$(aws iam list-policy-versions --policy-arn "$policy_arn" \
--query 'Versions[?IsDefaultVersion].VersionId' --output text --region us-east-1)
policy_doc=$(aws iam get-policy-version --policy-arn "$policy_arn" \
--version-id "$version_id" --query 'PolicyVersion.Document' --output json --region us-east-1)
if echo "$policy_doc" | grep -q '"Action".*"\*"' && \
echo "$policy_doc" | grep -q '"Resource".*"\*"'; then
echo "STILL FLAGGED: $policy_arn"
found_issues=1
fi
done
if [ $found_issues -eq 0 ]; then
echo "SUCCESS: No unattached policies with administrative privileges found"
fi
Run the Prowler check directly:
prowler aws --checks iam_customer_unattached_policy_no_administrative_privileges --region us-east-1
Additional Resources
- IAM Best Practices
- Granting Least Privilege
- IAM Access Analyzer Policy Validation
- Refining Permissions Using Access Advisor
Notes
- Prioritize deletion: If an unattached policy with
*:*is not actively needed, delete it rather than modifying it - Review attached policies too: This check only covers unattached policies. Consider also reviewing attached policies for overly broad permissions
- Use Access Analyzer: AWS IAM Access Analyzer can help identify unused permissions and validate policies against best practices
- Establish guardrails: Use Service Control Policies (SCPs) in AWS Organizations to prevent creation of policies with
*:*permissions - Peer review: Require approval for any new IAM policies before deployment to catch overly permissive policies early