Back to Checks
aws

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 Cloud
Run
prowler aws --checks cloudtrail_threat_detection_privilege_escalation

Recommendation

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 ID

Other

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