HackBlue

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.

12 min read · practice only with authorization

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.

Snippet
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 eventTime

The session’s timeline

Twenty-six calls. The first are reconnaissance that mostly failed. Two calls succeeded against one bucket the role legitimately reads.

Snippet
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.

Snippet
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 eventTime

Worked 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?