This issue has been assessed as high severity. Review affected configurations immediately.
CloudTrail records every API call made against your AWS account, across every service and region. That’s an enormous volume of data — and most of it is routine. The skill in AWS cloud threat hunting is knowing which events, combinations, and sequences indicate an attacker operating in your environment rather than a legitimate workload.
This guide covers the specific CloudTrail event patterns that matter most for detecting the phases of an AWS attack chain: initial access via credential theft, privilege escalation, persistence, and lateral movement.
Setting Up for Effective Hunting
Before hunting, ensure CloudTrail is actually capturing what you need:
# Verify CloudTrail is enabled across all regions with S3 data events
aws cloudtrail describe-trails --include-shadow-trails \
--query 'trailList[*].[Name,IsMultiRegionTrail,IncludeGlobalServiceEvents,S3BucketName]'
# Check if data events (S3 object-level, Lambda invoke) are enabled
aws cloudtrail get-event-selectors --trail-name <trail-name>
Without data events enabled, CloudTrail captures management events (API calls) but misses object-level S3 reads and Lambda invocations — both of which are critical for detecting exfiltration.
For serious threat hunting, route CloudTrail to Athena or a SIEM. Athena lets you run SQL against logs stored in S3 without ingesting them into a paid logging tier:
-- Athena setup: partition projection for CloudTrail logs
CREATE EXTERNAL TABLE cloudtrail_logs (
eventversion STRING, useridentity STRUCT<...>, eventtime STRING,
eventsource STRING, eventname STRING, awsregion STRING,
sourceipaddress STRING, errorcode STRING, errormessage STRING,
requestparameters STRING, responseelements STRING
)
PARTITIONED BY (account STRING, region STRING, year INT, month INT, day INT)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://your-cloudtrail-bucket/AWSLogs/';
Phase 1: Detecting Credential Compromise and Initial Access
Unusual GetCallerIdentity Calls
sts:GetCallerIdentity is frequently the first API call an attacker makes after obtaining credentials — it’s how they verify the credentials work and determine what account they’re in. Legitimate automation rarely calls this without purpose.
SELECT eventtime, sourceipaddress, useridentity.arn, useragent
FROM cloudtrail_logs
WHERE eventname = 'GetCallerIdentity'
AND sourceipaddress NOT IN (known_corporate_ips)
AND useragent NOT LIKE '%aws-sdk%'
ORDER BY eventtime DESC;
ConsoleLogin from Unexpected Locations
SELECT eventtime, sourceipaddress, useridentity.username,
json_extract_scalar(additionaleventdata, '$.MFAUsed') AS mfa_used,
errorcode
FROM cloudtrail_logs
WHERE eventname = 'ConsoleLogin'
AND eventsource = 'signin.amazonaws.com'
AND errorcode IS NULL -- successful logins
ORDER BY eventtime DESC;
Flag successful logins without MFA, from unfamiliar IPs, or at unusual hours.
Secrets Manager and SSM Parameter Access
Credential harvesting often targets Secrets Manager and SSM Parameter Store — attackers know that’s where databases, API keys, and service credentials live.
SELECT eventtime, useridentity.arn, eventname, sourceipaddress,
json_extract_scalar(requestparameters, '$.secretId') AS secret_accessed
FROM cloudtrail_logs
WHERE eventsource = 'secretsmanager.amazonaws.com'
AND eventname IN ('GetSecretValue', 'ListSecrets', 'DescribeSecret')
ORDER BY eventtime DESC;
A single principal listing all secrets followed by rapid GetSecretValue calls against multiple secrets is a high-confidence indicator of credential harvesting.
Phase 2: Detecting Privilege Escalation
IAM Policy Modifications
SELECT eventtime, useridentity.arn, eventname, sourceipaddress,
requestparameters
FROM cloudtrail_logs
WHERE eventsource = 'iam.amazonaws.com'
AND eventname IN (
'CreatePolicy', 'CreatePolicyVersion', 'PutUserPolicy', 'AttachUserPolicy',
'PutRolePolicy', 'AttachRolePolicy', 'AddUserToGroup', 'CreateRole',
'UpdateAssumeRolePolicy', 'PassRole'
)
ORDER BY eventtime DESC;
Pay particular attention to CreatePolicyVersion with setAsDefault=true — this is the canonical privilege escalation path where an attacker with iam:CreatePolicyVersion on a policy attached to a privileged role can silently add iam:* or AdministratorAccess to themselves.
Detecting PassRole Abuse
SELECT eventtime, useridentity.arn, requestparameters,
json_extract_scalar(requestparameters, '$.roleName') AS role_passed
FROM cloudtrail_logs
WHERE eventname = 'PassRole'
AND eventsource = 'iam.amazonaws.com'
ORDER BY eventtime DESC;
Cross-reference the role being passed against a list of highly-privileged roles. PassRole of an AdminRole to a new EC2 instance or Lambda function is a reliable privilege escalation indicator.
AdministratorAccess Policy Attachments
SELECT eventtime, useridentity.arn, eventname,
json_extract_scalar(requestparameters, '$.policyArn') AS policy_arn
FROM cloudtrail_logs
WHERE eventname IN ('AttachUserPolicy', 'AttachRolePolicy', 'AttachGroupPolicy')
AND requestparameters LIKE '%AdministratorAccess%'
ORDER BY eventtime DESC;
Any attachment of arn:aws:iam::aws:policy/AdministratorAccess outside of a known provisioning workflow is critical.
Phase 3: Detecting Persistence
New IAM Users, Keys, and Access Tokens
SELECT eventtime, useridentity.arn, eventname, sourceipaddress,
json_extract_scalar(requestparameters, '$.userName') AS new_user
FROM cloudtrail_logs
WHERE eventsource = 'iam.amazonaws.com'
AND eventname IN ('CreateUser', 'CreateAccessKey', 'CreateLoginProfile',
'CreateGroup', 'AddUserToGroup')
ORDER BY eventtime DESC;
Lambda Backdoor Deployment
Attackers persist via Lambda functions triggered by events, which run with IAM roles they’ve granted themselves. Detecting new Lambda deployments from unexpected principals:
SELECT eventtime, useridentity.arn, eventname, awsregion,
json_extract_scalar(requestparameters, '$.functionName') AS function_name,
json_extract_scalar(requestparameters, '$.role') AS execution_role
FROM cloudtrail_logs
WHERE eventsource = 'lambda.amazonaws.com'
AND eventname IN ('CreateFunction20150331', 'UpdateFunctionCode20150331v2',
'AddPermission20150331v2')
ORDER BY eventtime DESC;
CloudShell and Systems Manager Session Manager
Interactive shell access via CloudShell or SSM Session Manager creates a foothold that doesn’t require SSH keys or open security group ports:
SELECT eventtime, useridentity.arn, sourceipaddress, eventname
FROM cloudtrail_logs
WHERE (eventsource = 'cloudshell.amazonaws.com' AND eventname = 'CreateEnvironment')
OR (eventsource = 'ssm.amazonaws.com' AND eventname = 'StartSession')
ORDER BY eventtime DESC;
Phase 4: Detecting Exfiltration and Lateral Movement
S3 Bulk Downloads
Data events must be enabled in your trail for this query:
SELECT eventtime, useridentity.arn, sourceipaddress,
json_extract_scalar(requestparameters, '$.bucketName') AS bucket,
count(*) AS download_count
FROM cloudtrail_logs
WHERE eventname = 'GetObject'
AND eventsource = 's3.amazonaws.com'
AND errorcode IS NULL
GROUP BY eventtime, useridentity.arn, sourceipaddress,
json_extract_scalar(requestparameters, '$.bucketName')
HAVING count(*) > 100
ORDER BY download_count DESC;
Cross-Account Role Assumptions
Lateral movement in AWS often involves chaining role assumptions across accounts in the same organisation:
SELECT eventtime, useridentity.arn AS source_identity,
json_extract_scalar(requestparameters, '$.roleArn') AS assumed_role,
json_extract_scalar(requestparameters, '$.roleSessionName') AS session_name,
sourceipaddress
FROM cloudtrail_logs
WHERE eventname = 'AssumeRole'
AND eventsource = 'sts.amazonaws.com'
AND errorcode IS NULL
-- Flag cross-account assumptions (different account ID in the role ARN)
AND json_extract_scalar(requestparameters, '$.roleArn') NOT LIKE '%:ACCOUNT_ID:%'
ORDER BY eventtime DESC;
High-Priority Alert Combinations
The individual queries above help with investigation. For alerting, prioritise these combinations:
| Combination | Why it matters |
|---|---|
GetCallerIdentity → ListRoles → CreatePolicyVersion within 5 min | Classic recon → escalation sequence |
ConsoleLogin from new IP without MFA + IAM changes within 30 min | Compromised credential with immediate escalation |
CreateAccessKey on an existing high-privilege user | Backdoor access key creation |
PutBucketPolicy making bucket public + GetObject spike | Exfiltration via S3 public exposure |
AssumeRole from Lambda or EC2 to unrelated account + ListSecrets | Cross-account lateral movement to credential harvest |
Operationalising the Queries
For a team without a full SIEM, the minimum viable setup:
- CloudTrail to S3 — enabled globally, multi-region, including global services
- Athena on the CloudTrail S3 bucket — free tier covers moderate query volumes
- CloudWatch Alarms on the highest-priority events (new access key, policy attachment, public bucket) with SNS notification to a security email or Slack channel
- AWS Security Hub — aggregates GuardDuty, Config, and IAM Access Analyzer findings; provides a single pane for common detection logic without custom queries
AWS GuardDuty covers several of these patterns natively (credential use from unusual locations, enumeration calls, S3 exfiltration patterns). CloudTrail hunting complements GuardDuty by enabling custom logic, longer-horizon correlation, and investigation of events GuardDuty doesn’t flag.