Tools · AWS detection and investigation · 04 / 04
CloudTrail Lake investigation: everything one leaked role session did
Follow a GuardDuty credential-exfiltration finding into CloudTrail Lake, list every call made with the leaked access key, and check for persistence such as new users, keys, or role trust changes.
Pivot on the access key
Every CloudTrail event records the access key ID used. Temporary role keys start with ASIA. Filtering on that one value lists everything the session did, in order, regardless of which service it touched.
SELECT eventTime, eventSource, eventName, sourceIPAddress, errorCode,
element_at(requestParameters, 'bucketName') AS bucket
FROM <eds-id>
WHERE userIdentity.accessKeyId = 'ASIAEXAMPLE7QK2'
AND eventTime BETWEEN '2026-11-02 02:00:00' AND '2026-11-02 06:00:00'
ORDER BY eventTimeThe session’s timeline
Twenty-six calls. The first are reconnaissance that mostly failed. Two calls succeeded against one bucket the role legitimately reads.
03:10:11 sts GetCallerIdentity 198.51.100.200 - 03:12:40 s3 ListBuckets 198.51.100.200 AccessDenied 03:15:02 iam ListRoles 198.51.100.200 AccessDenied 03:20:17 s3 ListObjectsV2 198.51.100.200 - app-uploads 03:21:44 s3 GetObject 198.51.100.200 - app-uploads 03:30:09 iam CreateUser 198.51.100.200 AccessDenied
Checking for persistence
Even when the leaked session failed to create anything, check the whole account for changes that would let an outsider return: new users, new access keys, login profiles, and edits to role trust policies. This query looks across all principals in the window, not only the leaked key.
SELECT eventTime, eventName, userIdentity.arn, sourceIPAddress, errorCode
FROM <eds-id>
WHERE eventName IN ('CreateUser','CreateAccessKey','CreateLoginProfile','UpdateAssumeRolePolicy','AttachRolePolicy','PutRolePolicy')
AND eventTime > '2026-11-01 00:00:00'
ORDER BY eventTimeWorked conclusion
The leaked session read objects from app-uploads and failed every write to IAM. The persistence query shows only the platform pipeline’s expected changes. The ListObjectsV2 and GetObject rows exist only because this store also ingests S3 data events for app-uploads, so the report can name the objects read. Without data events those two rows would be missing, and the report would have to say object-level access cannot be confirmed.
The fix list follows from the evidence: require IMDSv2 on the instance, fix the SSRF route that exposed the credentials, and narrow the role to the prefix the application needs.
Diagram
Questions to ask of one leaked session
Each query answers one of them.
- What did it do?
Every event with that accessKeyId, in order.
- What did it reach?
Successful calls and the resources they named.
- Could it return?
Account-wide identity changes in the same window.
- What can’t we see?
Data events that were not enabled for the bucket.
Check yourself
1. Which field ties every call from one leaked role session together?
2. Why run the persistence query across all principals, not only the leaked key?
3. Data events were off for the bucket. What should the report say?