Cloud Security

Detecting Privilege Escalation in AWS: A CloudTrail Runbook

September 30, 2026 · 5 min read · By CloudAI Security
Detecting Privilege Escalation in AWS: A CloudTrail Runbook

Detecting privilege escalation in AWS comes down to watching a short list of CloudTrail events that healthy workloads almost never call. An attacker with a foothold in your account does not need a zero-day to become an administrator; they need one overly permissive IAM permission and a single API call to exercise it. Every one of those calls — creating a policy version, attaching a policy, minting an access key — lands in CloudTrail within minutes. Rhino Security Labs catalogued 21 distinct ways for a low-privilege principal to reach administrative permissions, and nearly every one of those paths produces a short, loud event trail in CloudTrail. Teams that catch escalation early alert on those events by name instead of waiting for anomaly models to flag them hours later.

Why Escalation Beats Exploits

Most cloud intrusions are configuration abuse, not vulnerability exploitation. The IAM service is working as designed when it lets an authorized caller modify policies; the abuse lives in how broad that authorization was. The canonical example from the Rhino research: a new policy version created with the –set-as-default flag becomes the default without ever requiring iam:SetDefaultPolicyVersion, so a single CreatePolicyVersion permission is enough to rewrite effective permissions in one call. The same pattern repeats across update and attach operations: SetDefaultPolicyVersion, AttachUserPolicy, PutRolePolicy and UpdateAssumeRolePolicyDocument each hand a caller the ability to widen privileges somewhere else in the account. Because these are normal administrative APIs, the defense cannot block them outright — it has to detect the context in which they fire.

The Events Worth Alerting On

The table below maps the highest-signal CloudTrail events to the escalation capability each one unlocks and the triage question that separates abuse from routine change management. Deploy pipelines legitimately call some of these actions, so compare each occurrence against your change window and the calling identity before paging.

CloudTrail eventWhat it enablesTriage question
CreatePolicyVersionFull control of effective permissions via –set-as-defaultDid the caller come from a known deployment role?
SetDefaultPolicyVersionReactivates an older, broader policy versionWas this version part of the last reviewed change?
AttachUserPolicy / PutUserPolicyGrants new permissions to any chosen principalWhy is a human attaching policies outside IaC?
CreateAccessKeyPersistent credentials for another userDoes the target user already hold two active keys?
UpdateAssumeRolePolicyDocumentEdits who may assume a roleWas the trust change requested through IaC?
PassRole with RunInstances or CreateFunctionMoves a powerful role onto attacker-controlled computeIs the destination resource in the known fleet?

Two detection details decide whether these alerts are usable. First, alert on the pairing, not the single event: PassRole alone is routine, but PassRole followed by RunInstances from an unfamiliar subnet is a classic takeover pattern documented in the Rhino Security Labs escalation research and its companion method catalog on GitHub. Second, baseline by identity: an event that is normal for your CI role is an emergency when it comes from a contractor account.

A Five-Step Response Runbook

When one of these alerts fires, work the sequence in order and resist the urge to investigate before containing.

  1. Freeze the caller. Attach an explicit deny-all inline policy to the suspected identity and revoke its active sessions; this stops in-flight escalation without destroying evidence.
  2. Establish the timeline. Pull every CloudTrail event from that caller in the preceding 24 hours, including GetCallerIdentity and any AssumeRole calls, to map what was read before anything was changed.
  3. Roll back the specific change. Delete rogue policy versions, detach unauthorized policies, deactivate any access keys created during the window, and revert modified trust policies.
  4. Hunt lateral movement. Search for AssumeRole calls made by the compromised identity toward roles it had never assumed before, and check for new Lambda functions, EC2 instances or notebooks created with passed roles.
  5. Close the enabling gap. Remove the permission pair that made escalation possible and add the scoped Resource element your policy was missing, so the same path cannot be retried with the next foothold.

Cutting the Blast Radius

Detection gets faster when the architecture limits how far a stolen session can travel. Role chaining caps any AWS CLI or API session at a maximum of one hour, even when the role itself allows a longer maximum session duration, according to the AWS STS AssumeRole reference and the IAM role chaining documentation; that cap is a real containment control, because a pivoted credential dies on its own within the hour. On the preventive side, condition keys such as aws:SourceIp and aws:username let admins restrict dangerous permissions to a known address range or to the caller’s own identity, which breaks most of the escalation paths in the catalog at the policy layer. Reviewing those scoping decisions on a schedule is more effective than any single alert, and if your account still leans on static keys, the approach described in retiring static cloud keys with workload identity federation removes an entire credential-theft class. Finally, your mean time to answer depends on what you logged in the first place — the decisions covered in a cloud logging strategy built for fast investigations determine whether step two of the runbook takes minutes or a day.

Sources