AWS persistence: four patterns to hunt after an incident
Rapid7's Jan Blažek maps four AWS persistence classes — new IAM users, assume-role edits, Lambda backdoors, federated tokens — with the CloudTrail signals to hunt for each.
Rapid7 published a detection-and-response walk-through this morning by Jan Blažek that groups AWS persistence into four classes: new IAM principals, tampered assume-role trust policies, Lambda functions wired to a public trigger, and federated session tokens issued via GetFederationToken. None of the four is novel on its own. What’s useful is that Blažek names the CloudTrail events that let you hunt each one without having to reason about the attack pattern from scratch — this is the kind of runbook I want printed and taped to the wall of any team responsible for an AWS estate they didn’t build.
Every one of these leaves footprints in CloudTrail. If your CloudTrail is not enabled in every region, with management events on, before you finish reading this: stop and go fix that. Everything below assumes you have the logs.
The four patterns
IAM user creation. The classic. An iam:CreateUser call, followed within minutes by any of iam:CreateAccessKey, iam:CreateLoginProfile, iam:AttachUserPolicy, iam:PutUserPolicy, or iam:AddUserToGroup, is the whole chain. Alert on CreateUser events where the target account has an established naming convention that the new user violates, or where the call comes from a source IP or role that has never created users before. Blažek’s example query keys on userName in requestParameters — a simple hunt is to list every CreateUser in the last 90 days and diff it against your provisioning system’s records.
Assume-role policy modification. Any call to iam:UpdateAssumeRolePolicy on a role you didn’t just create is worth a look, because the assume-role policy is the door — the permissions policy only matters once someone can walk through it. The specific pattern to hunt is a modification that adds an external account (a Principal block with an AWS account ID that isn’t yours) or that widens an existing trust relationship to "*". iam:CreateRole with an assume-role policy naming an unfamiliar account should trigger the same alert path.
Lambda function abuse. Two shapes. First, function URLs: lambda:CreateFunctionUrlConfig paired with lambda:AddPermission that grants FunctionUrlAuthType: NONE is a function anyone on the internet can invoke, which is very rarely what production code wants. Second, API Gateway integrations: apigateway:CreateIntegration or PutIntegration pointed at a Lambda you didn’t expect to be externally exposed. Both are legitimate patterns for real services — the signal isn’t the API call, it’s the combination of “sudden new invocation path” and “function that was never publicly reachable before.”
Federated session tokens. sts:GetFederationToken returns temporary credentials whose remaining lifetime you can’t shorten from the console. Revoking the underlying IAM user’s active sessions does not invalidate a token that’s already been issued — you have to attach an explicit deny policy that references the token’s aws:TokenIssueTime. Hunt for GetFederationToken calls where the calling user has no legitimate reason to be minting federated sessions, and if you find one during an active incident, assume the token is in the attacker’s hand until it naturally expires or you deny it out.
What to actually do
Standing up detection for all four of these is a weekend of detection-engineering work, not a quarter-long project. Order it like this:
- Assume-role policy changes first. They are the highest-leverage foothold — one edit hands persistent access to an external account without touching your user directory at all, and they’re rare in normal operations, so the alert-to-noise ratio is excellent.
GetFederationTokencalls second. These are the ones you can’t cleanly evict during an incident, and they’re almost always used by tooling that a defender should already know about. Alert on any call outside that allowlist.- Lambda function URLs and unexpected API Gateway integrations third. More false positives here — legit teams ship new endpoints all the time — but the “AuthType: NONE” flag is a good sharp filter.
- New IAM users last. Not because they don’t matter, but because if you have any IAM identity center or SSO discipline, this pattern should already be constrained enough that a new long-lived user is itself a signal.
Priority call
If you had an AWS incident in the last ninety days and you cleaned it up by rotating credentials and calling it done, walk back through CloudTrail for these four patterns before you close the ticket. Rotating a compromised key is credential hygiene — it doesn’t clear an assume-role policy that got edited on the way through, and it doesn’t invalidate a federation token that was minted while the attacker held the key. Blažek’s post gives you the exact API calls to search on. Use them.
Related: Miggo’s cross-tenant RabbitMQ OAuth leak in AWS-hosted MQ, Proofpoint on OAuth client-ID spoofing blinding Entra sign-in logs, and Sand Security on Writer.com cross-tenant session bleed — different clouds, same theme: identity is the perimeter, and the logs that cover it are the ones worth investing in.
Found this useful? Share it.


