Bucket exfiltration does not need malware. A stolen access key or an over-permissive role can list and read every object, and a policy change can expose a bucket to the internet. Management events show the permission changes, but you only see the reads if S3 data events are enabled, which is the first control to verify.
Signals at a Glance
| Signal | Where | ATT&CK |
|---|---|---|
Burst of GetObject or ListObjectsV2 by one principal | CloudTrail S3 data events | T1530 |
PutBucketPolicy, PutBucketAcl or DeletePublicAccessBlock making a bucket public | CloudTrail management events | T1530 |
| Reads from an unfamiliar IP, ASN or Tor exit by a long-lived access key | CloudTrail, GuardDuty | T1078.004 |
| Replication or copy of objects to a bucket in another account | CloudTrail, S3 replication config | T1537 |
GuardDuty Exfiltration:S3/AnomalousBehavior or Exfiltration:S3/MaliciousIPCaller | GuardDuty | T1530 |
CloudTrail Data Events
Data events must be enabled per bucket or per account, and they are billed, which is why many environments lack them. Without them, you can see who changed the bucket but not who read from it. A GetObject record carries the principal, source address and object key:
{
"eventSource": "s3.amazonaws.com",
"eventName": "GetObject",
"userIdentity": { "type": "IAMUser", "arn": "arn:aws:iam::111122223333:user/svc-report", "accessKeyId": "AKIA..." },
"sourceIPAddress": "203.0.113.45",
"userAgent": "[aws-cli/2.15.0 ...]",
"requestParameters": { "bucketName": "corp-finance-archive", "key": "2026/q3/ledger.parquet" },
"additionalEventData": { "bytesTransferredOut": 48211734 }
}
// tune/validate: the key fields to aggregate are userIdentity.arn, sourceIPAddress and bytesTransferredOut
Aggregate by principal and source address, and compare with the principal’s usual pattern. A reporting key that normally makes a few hundred reads from one address and now pulls tens of thousands from a hosting provider is the shape you want:
index=aws sourcetype="aws:cloudtrail" eventSource="s3.amazonaws.com" eventName IN ("GetObject","ListObjectsV2")
| bin _time span=15m
| stats count as reads sum(additionalEventData.bytesTransferredOut) as bytes_out
dc(requestParameters.key) as objects dc(sourceIPAddress) as ips
by _time, userIdentity.arn, requestParameters.bucketName
| where reads > 5000 OR bytes_out > 5000000000
| sort - bytes_out
``` tune/validate: lookup known data-pipeline principals and exclude by ARN, not by name ```
Exposure and Posture Changes
- Alert on
PutBucketPolicywith a wildcard principal,PutBucketAclgranting AllUsers or AuthenticatedUsers, andDeletePublicAccessBlock; GuardDuty reports related policy findings such asPolicy:S3/BucketPublicAccessGranted. - Keep S3 Block Public Access enforced at the account level, so exposure requires a visible, alertable change.
- Hunt for new IAM access keys,
CreateAccessKeyon privileged users, and role assumptions from new source addresses preceding the reads. - Turn on GuardDuty S3 protection and S3 server access logs as a second source, since they record requests that CloudTrail data events may not cover.
AWS CloudTrail and GuardDuty documentation, Splunk ESCU AWS analytic stories, Elastic AWS detection rules, and SigmaHQ cloud rules. Baseline your own environment — ETL jobs, backups and analytics services read large volumes of objects by design.
Build Detections That Fire
Mjolnir Security helps blue teams turn threat intelligence into high-fidelity detections across endpoint, network, identity and cloud telemetry.
- Detection Engineering We build and tune detections mapped to ATT&CK from your own telemetry.
- Threat Hunting as a Service Hypothesis-driven hunts for the behaviours on this page.
- 24/7 Incident Response Containment when a detection fires. Call +1 833 403 5875.
Stay ahead of emerging threats. Get notified when we publish new intelligence reports and advisories.
Subscribe to Alerts