Ensure No IAM Inline Policies Allow Privilege Escalation
Overview
This check identifies IAM inline policies that contain permission combinations enabling privilege escalation. Privilege escalation occurs when a user can leverage certain IAM permissions to gain higher access levels than originally intended, potentially reaching administrator-level access.
The check flags dangerous actions such as:
iam:CreatePolicyVersion- Create new versions of policiesiam:AttachUserPolicy/iam:AttachRolePolicy- Attach policies to users or rolesiam:PassRole- Pass roles to AWS servicessts:AssumeRole- Assume other rolesiam:UpdateAssumeRolePolicy- Modify who can assume a roleiam:PutUserPolicy/iam:PutRolePolicy- Add inline policies
Risk
When inline policies contain these dangerous permission combinations, attackers or compromised accounts can escalate their privileges to administrator level. This can lead to:
- Unauthorized data access: Reading secrets, databases, and sensitive files
- System compromise: Modifying application code, configurations, and security settings
- Service disruption: Deleting resources, disabling logging, or causing outages
- Persistent access: Creating backdoor credentials or roles for future access
Severity: High
Remediation Steps
Prerequisites
You need IAM permissions to view and modify policies. Specifically, you need permissions like iam:ListUserPolicies, iam:GetUserPolicy, iam:DeleteUserPolicy, and similar permissions for roles and groups.
Required IAM permissions
To perform this remediation, your IAM principal needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:ListUsers",
"iam:ListRoles",
"iam:ListGroups",
"iam:ListUserPolicies",
"iam:ListRolePolicies",
"iam:ListGroupPolicies",
"iam:GetUserPolicy",
"iam:GetRolePolicy",
"iam:GetGroupPolicy",
"iam:DeleteUserPolicy",
"iam:DeleteRolePolicy",
"iam:DeleteGroupPolicy",
"iam:PutUserPolicy",
"iam:PutRolePolicy",
"iam:PutGroupPolicy",
"iam:CreatePolicy",
"iam:AttachUserPolicy",
"iam:AttachRolePolicy",
"iam:AttachGroupPolicy"
],
"Resource": "*"
}
]
}
AWS Console Method
Step 1: Identify the problematic inline policy
- Open the IAM Console
- From the Prowler finding, note which IAM entity (user, role, or group) has the flagged inline policy
- In the left navigation, click Users, Roles, or Groups depending on the entity type
- Click on the entity name to open its details
Step 2: Review the inline policy
- Click the Permissions tab
- Under "Permissions policies," look for policies with a type of Inline
- Click the policy name to expand and view its JSON
- Look for dangerous actions listed in the Overview section above
Step 3: Remove or replace the inline policy
Option A: Delete the inline policy (if no longer needed)
- Click the X or Remove button next to the inline policy
- Confirm the deletion
Option B: Replace with a least-privilege managed policy
- Delete the inline policy (click X or Remove)
- Click Add permissions > Attach policies
- Either:
- Select an appropriate AWS managed policy that provides only the needed access
- Create a new customer managed policy with only the required permissions (see below)
Step 4: Create a secure replacement policy (if needed)
- In the IAM console, go to Policies in the left navigation
- Click Create policy
- Use the Visual editor or JSON editor to define permissions
- Follow these principles:
- Grant only the specific actions needed (avoid wildcards like
*) - Restrict resources to specific ARNs when possible
- Add conditions to further limit access
- Do NOT include any of the dangerous escalation actions unless absolutely required
- Grant only the specific actions needed (avoid wildcards like
- Give the policy a descriptive name and create it
- Attach this new managed policy to your user, role, or group
AWS CLI (optional)
List inline policies for users, roles, and groups
# List inline policies for a specific user
aws iam list-user-policies \
--user-name <username> \
--region us-east-1
# List inline policies for a specific role
aws iam list-role-policies \
--role-name <role-name> \
--region us-east-1
# List inline policies for a specific group
aws iam list-group-policies \
--group-name <group-name> \
--region us-east-1
View the contents of an inline policy
# Get user inline policy
aws iam get-user-policy \
--user-name <username> \
--policy-name <policy-name> \
--region us-east-1
# Get role inline policy
aws iam get-role-policy \
--role-name <role-name> \
--policy-name <policy-name> \
--region us-east-1
# Get group inline policy
aws iam get-group-policy \
--group-name <group-name> \
--policy-name <policy-name> \
--region us-east-1
Delete the problematic inline policy
# Delete user inline policy
aws iam delete-user-policy \
--user-name <username> \
--policy-name <policy-name> \
--region us-east-1
# Delete role inline policy
aws iam delete-role-policy \
--role-name <role-name> \
--policy-name <policy-name> \
--region us-east-1
# Delete group inline policy
aws iam delete-group-policy \
--group-name <group-name> \
--policy-name <policy-name> \
--region us-east-1
Create a secure managed policy as a replacement
First, create a policy document file (secure-policy.json):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}
Then create and attach the policy:
# Create the managed policy
aws iam create-policy \
--policy-name SecureLeastPrivilegePolicy \
--policy-document file://secure-policy.json \
--description "Least privilege policy without escalation permissions" \
--region us-east-1
# Attach to a user (replace account ID and user name)
aws iam attach-user-policy \
--user-name <username> \
--policy-arn arn:aws:iam::<account-id>:policy/SecureLeastPrivilegePolicy \
--region us-east-1
# Or attach to a role
aws iam attach-role-policy \
--role-name <role-name> \
--policy-arn arn:aws:iam::<account-id>:policy/SecureLeastPrivilegePolicy \
--region us-east-1
CloudFormation (optional)
Use this CloudFormation template to create a secure managed policy that follows least privilege principles. This replaces problematic inline policies.
AWSTemplateFormatVersion: '2010-09-09'
Description: Secure IAM managed policy following least privilege principles
Parameters:
PolicyName:
Type: String
Description: Name for the managed policy
Default: SecureLeastPrivilegePolicy
S3BucketArn:
Type: String
Description: ARN of the S3 bucket to grant access to
Default: arn:aws:s3:::my-example-bucket
Resources:
SecureManagedPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Ref PolicyName
Description: Least privilege policy without escalation permissions
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowS3ReadAccess
Effect: Allow
Action:
- s3:GetObject
- s3:ListBucket
Resource:
- !Ref S3BucketArn
- !Sub '${S3BucketArn}/*'
- Sid: AllowCloudWatchLogsRead
Effect: Allow
Action:
- logs:DescribeLogGroups
- logs:DescribeLogStreams
- logs:GetLogEvents
Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*'
Outputs:
PolicyArn:
Description: ARN of the created managed policy
Value: !Ref SecureManagedPolicy
Deploy this template:
aws cloudformation deploy \
--template-file secure-policy.yaml \
--stack-name secure-iam-policy \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
After creating the managed policy, manually delete the inline policies and attach this new policy to your IAM entities.
Terraform (optional)
Use this Terraform configuration to create a secure managed policy and attach it to IAM entities.
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
variable "s3_bucket_arn" {
description = "ARN of the S3 bucket to grant access to"
type = string
default = "arn:aws:s3:::my-example-bucket"
}
# Create a managed policy instead of inline policy
resource "aws_iam_policy" "secure_least_privilege" {
name = "SecureLeastPrivilegePolicy"
description = "Least privilege policy without escalation permissions"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowS3ReadAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
var.s3_bucket_arn,
"${var.s3_bucket_arn}/*"
]
}
]
})
}
# Attach the managed policy to a user (instead of inline policy)
resource "aws_iam_user_policy_attachment" "attach_secure_policy" {
user = "example-user"
policy_arn = aws_iam_policy.secure_least_privilege.arn
}
output "policy_arn" {
description = "ARN of the created managed policy"
value = aws_iam_policy.secure_least_privilege.arn
}
Important: Before applying, manually remove the existing inline policies or use terraform import to manage them.
Apply the configuration:
terraform init
terraform plan
terraform apply
Verification
After remediation, verify the fix:
- In the IAM console, check that the user/role/group no longer has inline policies with dangerous actions
- Re-run the Prowler check to confirm it passes:
prowler aws --check iam_inline_policy_allows_privilege_escalation
Advanced verification with AWS CLI
List all inline policies and verify none contain escalation permissions:
# Check a specific user has no inline policies
aws iam list-user-policies \
--user-name <username> \
--region us-east-1
# Expected output for a remediated user:
# {
# "PolicyNames": []
# }
# Verify attached managed policies instead
aws iam list-attached-user-policies \
--user-name <username> \
--region us-east-1
Additional Resources
- AWS IAM Best Practices
- Managed Policies vs Inline Policies
- IAM Access Analyzer for Least Privilege
- AWS Security Blog: Privilege Escalation
- Rhino Security Labs: AWS IAM Privilege Escalation
Notes
-
Prefer managed policies over inline policies: Managed policies are reusable, versioned, and easier to audit. AWS recommends using managed policies for most use cases.
-
Use IAM Access Analyzer: AWS IAM Access Analyzer can help generate least-privilege policies based on actual access patterns. This is a great way to right-size permissions.
-
Consider permissions boundaries: For users or roles that need some IAM permissions, use permissions boundaries to limit the maximum permissions they can have, even if they try to escalate.
-
Service Control Policies (SCPs): For multi-account environments, use SCPs to prevent dangerous IAM actions at the organization level.
-
Audit regularly: Use AWS Config rules or Prowler scheduled scans to continuously monitor for privilege escalation risks.
-
Test before removing permissions: Before deleting inline policies, ensure you understand what access the entity needs and have replacement policies in place to avoid breaking applications.