Back to Checks
No potential privilege escalation activity detected in CloudTrail
cloudtrail_threat_detection_privilege_escalation
Service: cloudtrailSeverity: critical
OfficialBuilt-inVerified
Categories:threat-detection
Resource Type:AwsCloudTrailTrail
CloudTrail activity is analyzed for identities executing high-risk actions linked to privilege escalation (e.g., Attach*Policy, PassRole, AssumeRole, CreateAccessKey). Identities exceeding a configurable share of such events within a recent time window are highlighted for investigation.
Risk
Escalation patterns can grant elevated entitlements, enabling:
- Confidentiality loss via unauthorized data/secret access
- Integrity compromise by changing IAM policies/roles
- Availability impact by tampering with logging or resources This also facilitates lateral movement and persistence.
Run this check with Prowler
Run in Prowler CloudRun
prowler aws --checks cloudtrail_threat_detection_privilege_escalationRecommendation
Apply least privilege and defense in depth:
- Restrict
PassRole,Attach*Policy,UpdateAssumeRolePolicy,CreateAccessKey - Enforce permission boundaries and SCPs
- Require MFA and change approvals
- Use multi-Region CloudTrail, immutable retention, and alerting on anomalous sequences
Remediation
Terraform
# SCP to block common IAM privilege-escalation actions
resource "aws_organizations_policy" "<example_resource_name>" {
name = "deny-iam-privesc"
type = "SERVICE_CONTROL_POLICY"
# Critical: Deny risky IAM actions to prevent future privesc
# Explanation: Blocks escalation techniques commonly seen in CloudTrail
content = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Deny",
Action = [
"iam:AttachUserPolicy",
"iam:AttachRolePolicy",
"iam:PutUserPolicy",
"iam:PutRolePolicy",
"iam:PutGroupPolicy",
"iam:AddUserToGroup",
"iam:CreateAccessKey",
"iam:CreateLoginProfile",
"iam:UpdateLoginProfile",
"iam:UpdateAssumeRolePolicy",
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion",
"iam:PassRole"
],
Resource = "*"
}
]
})
}
resource "aws_organizations_policy_attachment" "<example_resource_name>_attach" {
# Critical: Attach the SCP so it takes effect
policy_id = aws_organizations_policy.<example_resource_name>.id
target_id = "<example_resource_id>" # OU, Root, or Account ID
}NativeIaC
# CloudFormation: Organization SCP to block common IAM privilege-escalation actions
Resources:
<example_resource_name>:
Type: AWS::Organizations::Policy
Properties:
Name: deny-iam-privesc
Type: SERVICE_CONTROL_POLICY
# Critical: This SCP denies risky IAM actions often used for privilege escalation
# Explanation: Denying these actions organization-wide prevents future privesc activity detected by CloudTrail
Content: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"iam:AttachUserPolicy",
"iam:AttachRolePolicy",
"iam:PutUserPolicy",
"iam:PutRolePolicy",
"iam:PutGroupPolicy",
"iam:AddUserToGroup",
"iam:CreateAccessKey",
"iam:CreateLoginProfile",
"iam:UpdateLoginProfile",
"iam:UpdateAssumeRolePolicy",
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion",
"iam:PassRole"
],
"Resource": "*"
}
]
}
<example_resource_name>Attachment:
Type: AWS::Organizations::PolicyAttachment
Properties:
# Critical: Attach the SCP so it is enforced
PolicyId: !Ref <example_resource_name>
TargetId: <example_resource_id> # OU, Root, or Account IDOther
1In AWS Console, open IAM and identify the AWS identity shown in the Prowler finding (user or role ARN)
2If it is an IAM user:
3- Go to Security credentials > Access keys, set active keys to Inactive
4- Go to Permissions, detach all managed policies and delete inline policies
5- Go to Groups, remove the user from privileged groups
6- Go to Console password, delete the login profile
7If it is an IAM role:
8- Go to Permissions, detach managed policies and delete inline policies
9- Go to Trust relationships, remove principals that should not assume the role and save
10Re-run the scan after the detection window elapses to confirm no further privilege-escalation activity is detected
Check Code
import json
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
cloudtrail_client,
)
default_threat_detection_privilege_escalation_actions = [
"AddPermission",
"AddRoleToInstanceProfile",
"AddUserToGroup",
"AssociateAccessPolicy",
"AssumeRole",
"AttachGroupPolicy",
"AttachRolePolicy",
"AttachUserPolicy",
"ChangePassword",
"CreateAccessEntry",
"CreateAccessKey",
"CreateDevEndpoint",
"CreateEventSourceMapping",
"CreateFunction",
"CreateGroup",
"CreateJob",
"CreateKeyPair",
"CreateLoginProfile",
"CreatePipeline",
"CreatePolicyVersion",
"CreateRole",
"CreateStack",
"DeleteRolePermissionsBoundary",
"DeleteRolePolicy",
"DeleteUserPermissionsBoundary",
"DeleteUserPolicy",
"DetachRolePolicy",
"DetachUserPolicy",
"GetCredentialsForIdentity",
"GetId",
"GetPolicyVersion",
"GetUserPolicy",
"Invoke",
"ModifyInstanceAttribute",
"PassRole",
"PutGroupPolicy",
"PutPipelineDefinition",
"PutRolePermissionsBoundary",
"PutRolePolicy",
"PutUserPermissionsBoundary",
"PutUserPolicy",
"ReplaceIamInstanceProfileAssociation",
"RunInstances",
"SetDefaultPolicyVersion",
"UpdateAccessKey",
"UpdateAssumeRolePolicy",
"UpdateDevEndpoint",
"UpdateEventSourceMapping",
"UpdateFunctionCode",
"UpdateJob",
"UpdateLoginProfile",
]
class cloudtrail_threat_detection_privilege_escalation(Check):
def execute(self):
findings = []
threshold = cloudtrail_client.audit_config.get(
"threat_detection_privilege_escalation_threshold", 0.2
)
threat_detection_minutes = cloudtrail_client.audit_config.get(
"threat_detection_privilege_escalation_minutes", 1440
)
privilege_escalation_actions = cloudtrail_client.audit_config.get(
"threat_detection_privilege_escalation_actions",
default_threat_detection_privilege_escalation_actions,
)
potential_privilege_escalation = {}
found_potential_privilege_escalation = False
multiregion_trail = None
# Check if any trail is multi-region so we only need to check once
for trail in cloudtrail_client.trails.values():
if trail.is_multiregion:
multiregion_trail = trail
break
trails_to_scan = (
cloudtrail_client.trails.values()
if not multiregion_trail
else [multiregion_trail]
)
for trail in trails_to_scan:
for event_name in privilege_escalation_actions:
for event_log in cloudtrail_client._lookup_events(
trail=trail,
event_name=event_name,
minutes=threat_detection_minutes,
):
event_log = json.loads(event_log["CloudTrailEvent"])
if (
"arn" in event_log["userIdentity"]
): # Ignore event logs without ARN since they are AWS services
if (
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
) not in potential_privilege_escalation:
potential_privilege_escalation[
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
] = set()
potential_privilege_escalation[
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
].add(event_name)
for aws_identity, actions in potential_privilege_escalation.items():
identity_threshold = round(
len(actions) / len(privilege_escalation_actions), 2
)
aws_identity_type = aws_identity[1]
aws_identity_arn = aws_identity[0]
if len(actions) / len(privilege_escalation_actions) > threshold:
found_potential_privilege_escalation = True
report = Check_Report_AWS(
metadata=self.metadata(), resource=cloudtrail_client.trails
)
report.region = cloudtrail_client.region
report.resource_id = aws_identity_arn.split("/")[-1]
report.resource_arn = aws_identity_arn
report.status = "FAIL"
report.status_extended = f"Potential privilege escalation attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with a threshold of {identity_threshold}."
findings.append(report)
if not found_potential_privilege_escalation:
report = Check_Report_AWS(
metadata=self.metadata(), resource=cloudtrail_client.trails
)
report.region = cloudtrail_client.region
report.resource_id = cloudtrail_client.audited_account
report.resource_arn = cloudtrail_client._get_trail_arn_template(
cloudtrail_client.region
)
report.status = "PASS"
report.status_extended = (
"No potential privilege escalation attack detected."
)
findings.append(report)
return findings