Skip to content

This tool is not affiliated with, endorsed by or sponsored by Amazon Web Services, Inc. or Amazon.com, Inc. AWS, Amazon Web Services, CloudTrail and GuardDuty are trademarks of Amazon.com, Inc. or its affiliates. Other names are trademarks of their respective owners.

S3 data exfiltration detection: what the logs can prove

Prove or rule out S3 data theft: CloudTrail data events, S3 server access logs, public bucket policies, shared snapshots, and what you cannot see without them.

Published on 5 min read

TL;DR. "Did they take the data?" can only be answered for S3 objects if CloudTrail S3 data events or S3 server access logs were enabled before the incident. With them: count GetObject per principal, bucket and hour, sum bytesTransferredOut / Bytes Sent, and list the object keys. Without them: you can show the attacker could read (permissions, ListBuckets, ListObjects is a data event too) and whether a bucket was made public or a snapshot shared (management events), but not which objects left. Also check the channels that need no download at all: public policies, ACLs, shared EBS/RDS snapshots and AMIs.

Data theft is the question lawyers, customers and regulators ask first, and the one where log coverage matters most. This post covers what each AWS log source can prove about S3 and snapshot exfiltration, and how to report honestly when it cannot.

Two sources for object reads

CloudTrail S3 data eventsS3 server access logs
Enabled by defaultNo — must be selected on a trail or event data store (docs)No — per bucket (docs)
FormatCloudTrail JSON, same files as management eventsSpace-delimited text, one line per request
IdentityFull userIdentity (ARN, access key, session)Requester ARN or - for anonymous
Volume transferredadditionalEventData.bytesTransferredOutBytes Sent field
DeliveryAbout five minutes, like other CloudTrail eventsBest effort, usually within a few hours; completeness not guaranteed
CostCharged per data eventStorage of the log objects

Having both is ideal: CloudTrail gives reliable identity, access logs give an independent second record. Having either is enough to answer the question.

Reading CloudTrail data events

A data event for an object read looks like this (trimmed):

{
  "eventSource": "s3.amazonaws.com",
  "eventName": "GetObject",
  "eventCategory": "Data",
  "userIdentity": {"arn": "arn:aws:iam::111122223333:user/backup-admin", "accessKeyId": "AKIA..."},
  "sourceIPAddress": "203.0.113.77",
  "requestParameters": {"bucketName": "acme-customer-exports", "key": "exports/customers-0001.csv.gz"},
  "additionalEventData": {"bytesTransferredOut": 12345678}
}

Questions and how to answer them:

  • How many objects? Count distinct requestParameters.key per principal and bucket.
  • How much data? Sum bytesTransferredOut. Partial reads (range requests) count their actual bytes.
  • Which objects? The key list is your disclosure inventory. Export it.
  • From where? sourceIPAddress and userAgent: a boto3 client on a VPS pulling 300 objects in 20 minutes is not your backup job.
  • Before the reads: ListObjects / ListObjectsV2 (also data events), and management events ListBuckets, GetBucketPolicy, GetBucketAcl, GetBucketLocation from the same principal.

The mapping is MITRE ATT&CK T1530 Data from Cloud Storage.

Reading S3 server access logs

Server access logs have one line per request; the fields that matter here are the bucket, time, remote IP, requester, operation, key, HTTP status and bytes sent (log format).

79a59df9... acme-customer-exports [14/Sep/2026:09:15:04 +0000] 203.0.113.77
arn:aws:iam::111122223333:user/backup-admin 3E57427F3EXAMPLE REST.GET.OBJECT
exports/2026/customers/customers-0001.csv.gz "GET /exports/... HTTP/1.1" 200 - 12345678 ...

(Wrapped here for readability; each record is a single line.)

Filter on REST.GET.OBJECT with status 200 or 206, group by requester and bucket. A requester of - means an anonymous request — expected for a public website bucket, a leak for anything else.

Exfiltration without downloads

Some of the most effective data theft on AWS never produces a GetObject in your account, because the attacker makes the data reachable and copies it from elsewhere. These are all management events, so they are visible even without data events:

TechniqueEventsATT&CK
Bucket made publicPutBucketPolicy with "Principal": "*" and no restricting conditionT1530
Public ACLPutBucketAcl / PutObjectAcl granting AllUsers or AuthenticatedUsersT1530
Guard rail removedDeleteBucketPublicAccessBlock, DeleteAccountPublicAccessBlock, weakened PutPublicAccessBlockT1530
EBS snapshot sharedModifySnapshotAttribute adding a foreign account or all to createVolumePermissionT1537
AMI sharedModifyImageAttribute with launchPermissionT1537
RDS snapshot sharedModifyDBSnapshotAttribute / ModifyDBClusterSnapshotAttribute with restoreT1537
Replication to an outside bucketPutBucketReplicationT1537

A shared snapshot means the data can be copied into the attacker's account with no further trace in yours. Treat its content as disclosed.

When you have neither source

Be precise in the report. You can state:

  • which principals had permission to read which buckets (IAM policies, bucket policies);
  • whether the attacker listed buckets or read bucket policies (management events);
  • whether any bucket was made public or any snapshot shared;
  • whether network flows from your instances show large outbound transfers (VPC Flow Logs analysis) — useful for data staged on EC2, not for direct S3 downloads, which do not traverse your VPC.

You cannot state that objects were not read. "No evidence of access" is only meaningful when the evidence could have existed. The limitations post covers how to write this.

Notification clock

If personal data may be involved, legal deadlines start running from awareness, not from the end of the investigation — under the GDPR, 72 hours for notifying the supervisory authority (Article 33). Involve your data protection officer early, and hand them the object list as soon as you have it.

In the analyzer

AWS Forensics flags bulk downloads from either source (100+ objects from one bucket by one principal within an hour), anonymous downloads from public addresses, public bucket policies and ACLs, Block Public Access removed, snapshots or AMIs shared with other accounts, and secrets read in bulk. The coverage notes say explicitly when neither data events nor access logs were provided. Pivot on the bucket in the Entities tab to see every request against it, from both sources. For the full context, start from the incident response overview.

Related articles

What CloudTrail, VPC Flow Logs and S3 access logs do not record, where baselines fail, and how to write honest conclusions when evidence is missing.
A fictional AWS incident investigated from its logs: leaked key, recon, backdoor admin, GuardDuty deleted, 320 S3 objects taken, GPU mining in Singapore.
CloudTrail log analysis in your browser: load the exports, read the verdict and findings, pivot on keys and IPs, build the timeline, then remediate.

This tool is not affiliated with, endorsed by or sponsored by Amazon Web Services, Inc. or Amazon.com, Inc. AWS, Amazon Web Services, CloudTrail and GuardDuty are trademarks of Amazon.com, Inc. or its affiliates. Other names are trademarks of their respective owners.