PRACTITIONER GUIDE | CLOUD SECURITY
Practitioner GuideUpdated 13 min read

AWS CloudTrail Threat Hunting with Athena: Query Patterns for Detecting IAM Abuse, Lateral Movement, and Data Exfiltration

97%
of AWS account compromises involve at least one IAM API call (CreateAccessKey, AssumeRole, PutUserPolicy) that appears in CloudTrail
$9.44B
stolen from AWS environments annually through compromised IAM credentials used for cryptomining and data theft
90 days
default CloudTrail event history retention in the console -- Athena queries against S3-delivered logs can cover 365+ days
1-5 min
average CloudTrail delivery lag from API call to S3 object availability, making Athena unsuitable for real-time detection but ideal for investigation

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

CloudTrail is the most complete record of what happened in an AWS account, but querying it at scale requires Athena. The console's 90-day event history is insufficient for threat hunting and too slow for bulk analysis. With Athena configured against your CloudTrail S3 bucket, you can query months of API call history in seconds using SQL. This guide covers the Athena setup and the query patterns that surface the attacker activity most commonly found in AWS compromises.

Setting Up Athena for CloudTrail Queries

Before querying, partition the CloudTrail table for performance:

-- Create the CloudTrail table in Athena
CREATE EXTERNAL TABLE cloudtrail_logs (
  eventVersion STRING,
  userIdentity STRUCT<
    type: STRING,
    principalId: STRING,
    arn: STRING,
    accountId: STRING,
    invokedBy: STRING,
    accessKeyId: STRING,
    userName: STRING,
    sessionContext: STRUCT<
      sessionIssuer: STRUCT<
        type: STRING,
        principalId: STRING,
        arn: STRING,
        accountId: STRING,
        userName: STRING
      >,
      attributes: STRUCT<
        mfaAuthenticated: STRING,
        creationDate: STRING
      >
    >
  >,
  eventTime STRING,
  eventSource STRING,
  eventName STRING,
  awsRegion STRING,
  sourceIPAddress STRING,
  userAgent STRING,
  errorCode STRING,
  errorMessage STRING,
  requestParameters STRING,
  responseElements STRING,
  resources ARRAY<STRUCT<arn:STRING,accountId:STRING,type:STRING>>,
  recipientAccountId STRING
)
PARTITIONED BY (region STRING, year STRING, month STRING, day STRING)
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/YOUR_ACCOUNT_ID/CloudTrail/';

The quickest approach is to use the AWS-provided CloudFormation template that creates the table automatically: in the Athena console, go to Query editor > CloudTrail > Create table. Load partitions for the time range you want to query:

MSCK REPAIR TABLE cloudtrail_logs;

IAM Abuse Detection: Key Creation, Policy Attachment, and Role Assumption

The highest-priority CloudTrail events for IAM abuse detection:

Find new IAM access keys created (possible persistence):

SELECT
  eventTime,
  userIdentity.userName AS actor,
  userIdentity.accessKeyId AS actor_key,
  sourceIPAddress,
  json_extract_scalar(requestParameters, '$.userName') AS target_user
FROM cloudtrail_logs
WHERE eventName = 'CreateAccessKey'
  AND year = '2026' AND month = '06'
ORDER BY eventTime DESC;

New access keys created for users other than the actor are suspicious. An attacker who compromised one IAM key often creates a second key as persistence.

Find inline policy attachments (bypass SCP-controlled managed policies):

SELECT
  eventTime,
  userIdentity.userName AS actor,
  sourceIPAddress,
  json_extract_scalar(requestParameters, '$.userName') AS target_user,
  json_extract_scalar(requestParameters, '$.policyName') AS policy_name
FROM cloudtrail_logs
WHERE eventName IN ('PutUserPolicy', 'PutRolePolicy', 'PutGroupPolicy')
  AND year = '2026' AND month = '06'
ORDER BY eventTime DESC;

Inline policy attachments that grant AdministratorAccess or iam:* permissions are critical findings.

Find cross-account role assumptions:

SELECT
  eventTime,
  userIdentity.principalId,
  userIdentity.arn AS source_identity,
  json_extract_scalar(requestParameters, '$.roleArn') AS assumed_role,
  sourceIPAddress
FROM cloudtrail_logs
WHERE eventName = 'AssumeRole'
  AND userIdentity.accountId != recipientAccountId
  AND year = '2026' AND month = '06'
ORDER BY eventTime DESC;
Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

S3 Data Exfiltration Detection Patterns

S3 GetObject events are the primary indicator of data exfiltration. The challenge is volume: legitimate applications generate millions of GetObject calls. Use anomaly-based queries rather than absolute threshold queries:

Find IAM users or roles accessing S3 buckets they have never accessed before (last 30 days vs prior 30 days):

-- Current 30 days
WITH current_period AS (
  SELECT DISTINCT
    userIdentity.arn,
    json_extract_scalar(requestParameters, '$.bucketName') AS bucket
  FROM cloudtrail_logs
  WHERE eventName = 'GetObject'
    AND eventSource = 's3.amazonaws.com'
    AND year = '2026' AND month = '06'
),
-- Prior 30 days baseline
prior_period AS (
  SELECT DISTINCT
    userIdentity.arn,
    json_extract_scalar(requestParameters, '$.bucketName') AS bucket
  FROM cloudtrail_logs
  WHERE eventName = 'GetObject'
    AND eventSource = 's3.amazonaws.com'
    AND year = '2026' AND month = '05'
)
SELECT c.arn, c.bucket
FROM current_period c
LEFT JOIN prior_period p ON c.arn = p.arn AND c.bucket = p.bucket
WHERE p.arn IS NULL;

Find high-volume S3 GetObject calls from a single identity in a short window:

SELECT
  userIdentity.arn,
  sourceIPAddress,
  date_trunc('hour', from_iso8601_timestamp(eventTime)) AS hour_bucket,
  COUNT(*) AS get_count,
  COUNT(DISTINCT json_extract_scalar(requestParameters, '$.bucketName')) AS distinct_buckets
FROM cloudtrail_logs
WHERE eventName = 'GetObject'
  AND eventSource = 's3.amazonaws.com'
  AND year = '2026' AND month = '06'
GROUP BY 1, 2, 3
HAVING COUNT(*) > 1000
ORDER BY get_count DESC;

Console Login Anomaly Detection

Console logins without MFA, from unusual geolocations, or outside business hours are early indicators of credential compromise:

Find console logins without MFA:

SELECT
  eventTime,
  userIdentity.userName,
  sourceIPAddress,
  json_extract_scalar(additionalEventData, '$.MFAUsed') AS mfa_used,
  awsRegion
FROM cloudtrail_logs
WHERE eventName = 'ConsoleLogin'
  AND json_extract_scalar(additionalEventData, '$.MFAUsed') = 'No'
  AND errorCode IS NULL
  AND year = '2026' AND month = '06'
ORDER BY eventTime DESC;

Find root account console logins (should be near zero):

SELECT
  eventTime,
  userIdentity.type,
  sourceIPAddress,
  json_extract_scalar(additionalEventData, '$.MFAUsed') AS mfa_used
FROM cloudtrail_logs
WHERE eventName = 'ConsoleLogin'
  AND userIdentity.type = 'Root'
  AND year = '2026' AND month = '06'
ORDER BY eventTime DESC;

Any root login without a documented break-glass reason is a critical finding.

Find API calls from unusual source IPs for a given IAM user (new IP in last 7 days):

WITH baseline AS (
  SELECT DISTINCT userIdentity.userName, sourceIPAddress
  FROM cloudtrail_logs
  WHERE year = '2026' AND month = '05'
    AND userIdentity.type = 'IAMUser'
),
recent AS (
  SELECT DISTINCT userIdentity.userName, sourceIPAddress
  FROM cloudtrail_logs
  WHERE year = '2026' AND month = '06'
    AND userIdentity.type = 'IAMUser'
)
SELECT r.userName, r.sourceIPAddress AS new_ip
FROM recent r
LEFT JOIN baseline b ON r.userName = b.userName AND r.sourceIPAddress = b.sourceIPAddress
WHERE b.userName IS NULL
  AND r.sourceIPAddress NOT LIKE '10.%'
  AND r.sourceIPAddress NOT LIKE '172.16.%'
ORDER BY r.userName;

Correlating with GuardDuty and Building a Hunt Workflow

GuardDuty provides pre-built detection rules that surface known-bad patterns (cryptocurrency mining, Tor exit node usage, S3 bucket policy changes, IAM credential exfiltration). CloudTrail provides the full API call context that GuardDuty findings summarize.

When a GuardDuty finding fires, use CloudTrail to expand the investigation:

-- For a GuardDuty finding about a specific IAM principal:
-- Retrieve all API calls by that principal in the surrounding 4-hour window
SELECT
  eventTime,
  eventName,
  eventSource,
  sourceIPAddress,
  awsRegion,
  errorCode,
  json_extract_scalar(requestParameters, '$.bucketName') AS s3_bucket,
  json_extract_scalar(requestParameters, '$.roleName') AS iam_role
FROM cloudtrail_logs
WHERE userIdentity.arn = 'arn:aws:iam::123456789012:user/compromised-user'
  AND eventTime BETWEEN '2026-06-10T14:00:00Z' AND '2026-06-10T18:00:00Z'
ORDER BY eventTime ASC;

Build a threat hunt workflow:

  1. Weekly: run the high-volume S3 GetObject query and new IAM access key query, review results
  2. Daily: run the root login and console login without MFA queries, alert on any result
  3. On-demand for GuardDuty findings: run the principal timeline query for the flagged identity
  4. Monthly: run the new S3 access query (new buckets accessed by existing identities) as a low-and-slow exfiltration hunt

For automation, use AWS Lambda triggered by an EventBridge schedule to run these queries and send results to a Slack channel or SIEM via SNS.

The bottom line

CloudTrail contains the full timeline of every AWS compromise, but most organizations only look at it after they already know something went wrong. Running the IAM key creation, cross-account role assumption, and high-volume S3 queries weekly surfaces attacker activity that evades real-time alerting. Set up Athena against your CloudTrail S3 bucket, run the eight queries in this guide on a schedule, and treat any result from the root login or console-without-MFA queries as an immediate incident.

Frequently asked questions

How much does it cost to run Athena queries against CloudTrail logs?

Athena charges $5 per terabyte of data scanned. CloudTrail logs are typically 1-5 GB per month per active account. For most organizations, running daily threat hunt queries against 90 days of CloudTrail logs costs under $5 per month. Use partitioning (year/month/day) in your WHERE clause to avoid scanning the entire log history for every query. A query without a date filter against 12 months of logs can scan 10x more data and cost 10x more than a properly partitioned query.

What is the difference between CloudTrail management events and data events?

Management events record control-plane API calls: creating and deleting resources, modifying IAM policies, configuring security groups. They are enabled by default in CloudTrail and cover the IAM abuse and privilege escalation patterns. Data events record data-plane API calls: S3 GetObject, PutObject, Lambda invocations. Data events are not enabled by default and generate much higher volume (potentially billions of events per day for active S3 buckets). Enable data events for your highest-sensitivity S3 buckets and Lambda functions, not for all resources, to keep costs manageable.

Can I use AWS Security Lake instead of Athena for CloudTrail threat hunting?

AWS Security Lake (built on Amazon S3 with OCSF schema normalization) can ingest CloudTrail logs alongside other security data sources and is queryable via Athena. The advantage over raw CloudTrail in Athena is the normalized schema (OCSF) that allows cross-source correlation: you can join CloudTrail events with VPC Flow Logs and Route 53 query logs in a single query. For organizations already using Security Lake, it is the preferred approach. For those who are not, raw CloudTrail in Athena is faster to set up and sufficient for the queries in this guide.

How do I detect if an attacker disabled or modified CloudTrail to cover their tracks?

CloudTrail modifications are themselves logged in CloudTrail (as long as at least one trail was active when the change was made). Query for StopLogging, DeleteTrail, UpdateTrail, and PutEventSelectors events. GuardDuty also has a built-in finding (Stealth:IAMUser/CloudTrailLoggingDisabled) that alerts when logging is stopped. For defense in depth: enable CloudTrail log file integrity validation (detects log file tampering after delivery to S3), use an SNS topic to alert on StopLogging events via a CloudWatch Events rule, and ensure your CloudTrail S3 bucket has MFA delete enabled and Object Lock configured.

What is the most efficient Athena query structure for hunting across months of CloudTrail data?

Partition your CloudTrail S3 data using Athena partition projection on the year/month/day/region path components. Without partitioning, every query scans all objects in the bucket regardless of the time range filter. With partition projection configured correctly, a query with `WHERE year='2026' AND month='06'` scans only June 2026 data. Use CREATE TABLE with `PARTITIONED BY (year string, month string, day string, region string)` and enable partition projection in the table properties. For cost control, always include partition predicates in WHERE clauses and use `LIMIT` during development to avoid scanning full result sets unnecessarily.

How do I detect IAM credential theft via CloudTrail when the attacker uses legitimate credentials?

Credential theft with valid credentials leaves behavioral anomalies rather than outright errors. Key indicators in CloudTrail: API calls from a geographic location or ASN never seen for that principal (use the userAgent and sourceIPAddress fields), API calls at unusual times for the account (create a baseline of normal access hours per principal), API enumeration patterns (GetCallerIdentity followed by ListBuckets, DescribeInstances, ListRoles in rapid succession within minutes of first use), and new access key usage combined with account enumeration. Athena query template: SELECT useridentity.arn, sourceipaddress, eventtime, eventname FROM cloudtrail_logs WHERE eventname IN ('GetCallerIdentity','ListBuckets','ListRoles','DescribeInstances') AND eventtime > '2026-01-01' ORDER BY useridentity.arn, eventtime. Cluster results by ARN and look for enumeration bursts.

Sources & references

  1. AWS: Querying AWS CloudTrail logs with Amazon Athena
  2. AWS: CloudTrail event reference
  3. Datadog: AWS CloudTrail threat hunting guide
  4. Invictus IR: AWS CloudTrail threat hunting queries

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.