← All articles
Cloud Security 8 min read

S3 Data Exposure Post-Mortem: How Public Buckets Still Happen in 2025

Post-mortem on a public S3 bucket exposing customer PII. The 36-day exposure timeline, root cause chain across five failed controls, and what modern CSPM catches that manual reviews miss.

CloudDefender Team ·

This post-mortem is a fictionalized composite based on common S3 misconfiguration patterns documented in public breach disclosures. All company names and specific figures are illustrative. The technical failure chain is drawn directly from real-world incident patterns.

The S3 public bucket misconfiguration is not a novel attack. It has appeared in dozens of high-profile breaches over the past decade. Despite this, organizations continue to expose data through the same fundamental failure: a developer sets a bucket to public access for a quick test, the change is never reverted, and no control exists to catch it.

What makes this post-mortem worth examining is not the misconfiguration itself — it is the compounding failure chain that turned a five-minute developer shortcut into a 36-day exposure window. Understanding this failure chain is more valuable than any individual control recommendation. Security programs fail not because one control is missing, but because multiple layers of defense are absent simultaneously.

36-Day Exposure Window: Timeline of EventsDay 0Bucket createdpublic-read ACLDay 347k PII recordsloaded to bucketDay 14Threat actor scannerdiscovers bucketDays 15–35847 GetObject callsNo alert firedDay 36Customer reportsbreach discovered
36 days elapsed between the misconfiguration (Day 0) and discovery (Day 36). The bucket was exfiltrated silently for 21 days because no alert fired on the 847 S3 GetObject calls from external IP addresses.

The Incident Timeline

Day 0. A developer creates an S3 bucket named acme-staging-integration to support a third-party API integration test. To avoid dealing with presigned URLs during the initial testing phase, they set the bucket ACL to public-read. They intend to revert this once the test completes. The integration test works. The developer moves on to the next ticket. The ACL remains.

Day 3. The staging environment pipeline runs its nightly data sync. A copy of the customer database — stripped of financial information but retaining names, email addresses, phone numbers, and partial billing addresses — is loaded into the bucket for integration testing. Approximately 47,000 customer records are now publicly accessible on the internet.

Day 14. An automated S3 enumeration scanner — one of dozens that continuously probe publicly accessible AWS resources — discovers the bucket. The scanner catalogs the bucket’s contents and sells the finding to a threat actor who specializes in PII monetization.

Days 15–35. Over 21 days, 847 s3:GetObject calls download customer records from the bucket. The requests originate from a rotating set of IP addresses across multiple countries. S3 server access logs record every request. No alert fires. No one reviews the logs. The data is transferred, processed, and listed on a data breach marketplace.

Day 36. A customer contacts support reporting that their email address and phone number appear in phishing messages that reference their relationship with the company. The security team searches threat intelligence feeds and finds their data listed for sale. Incident response begins.

Day 38. Root cause is identified. The bucket ACL is reverted to private. The data, however, has been widely distributed. Making the bucket private does not un-exfiltrate the 47,000 customer records already downloaded.

The Root Cause Chain: Five Compounding Failures

The breach did not result from a single control failure. It resulted from five independent controls all being absent simultaneously. Each would have been individually sufficient to prevent it.

Failure 1 — No account-level S3 Block Public Access. AWS provides an account-level Block Public Access setting that prevents any bucket from being made publicly accessible regardless of bucket-level ACL settings. This is a single API call to enable:

aws s3control put-public-access-block \
  --account-id YOUR_ACCOUNT_ID \
  --public-access-block-configuration \
  'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true'

Had it been enabled, the developer’s public-read ACL would have been silently overridden and no public access would have been granted.

Failure 2 — No baseline bucket policy denying public access. Even without account-level Block Public Access, a bucket policy with an explicit deny on s3:GetObject for principals other than authorized services provides a second layer. Neither the developer nor any automated process created such a policy as part of bucket provisioning.

Failure 3 — S3 data event logging not enabled. S3 server access logs were enabled — they recorded the 847 GetObject calls. But server access logs are stored in a secondary bucket and not integrated with CloudTrail. CloudTrail data event logging for S3, which would have generated CloudTrail events for every GetObject and enabled EventBridge alerting, was not enabled due to cost concerns. The monthly cost difference for data event logging on this bucket: approximately $12.

Failure 4 — No Amazon Macie classification. Macie was not enabled on the account. A Macie scan of the bucket on Day 3 would have detected the PII within 24 hours and generated a Security Hub finding: SensitiveData:S3Object/Personal. This finding, routed through EventBridge, would have triggered an alert before any external party discovered the bucket.

Failure 5 — No AWS Config rule for public S3 ACLs. The AWS Config managed rule s3-bucket-public-read-prohibited evaluates S3 bucket ACLs on creation and on change. Had this rule been enabled, it would have generated a non-compliance finding on Day 0 within minutes of the bucket being created with a public ACL. The bucket would have been reverted on Day 0 instead of Day 38.

What Modern CSPM Catches

Each of the five failed controls above is detectable by continuous cloud security posture management:

ControlCSPM DetectionTime to Alert
Account-level Block Public AccessSecurity Hub S3.1 controlContinuous
Public bucket ACLConfig s3-bucket-public-read-prohibitedWithin 5 minutes of creation
External GetObject callsCloudTrail data events + EventBridgeWithin seconds of first call
PII in S3Macie automated discoveryWithin 24 hours of object upload
Security posture scoreSecurity Hub compliance scoreReal-time

A CSPM platform running these five controls in tandem would have caught this breach on Day 0 — not Day 36.

The Financial Impact (Illustrative)

For organizations of similar size (~150,000 customers), breach costs for a 47,000-customer PII exposure typically break down across:

Total: $1.9M–$2.8M from a misconfiguration that took approximately 30 seconds to introduce.

The five CSPM controls described above — S3 Block Public Access, Config rule, CloudTrail data events, Macie, Security Hub — cost approximately $300–$500 per month in AWS service costs, with approximately two days of engineering time to implement fully.

The calculus is not subtle.


CloudDefender runs continuous S3 posture checks across all accounts, fires within minutes of any bucket receiving a public ACL or policy, and correlates Macie PII findings with access patterns to identify high-risk data exposure before external parties discover it.

CloudDefender

Defend your cloud. Continuously.

CloudDefender Suite gives security teams continuous posture management, threat detection, and compliance automation across AWS, Azure, and GCP — with zero false-positive fatigue.

Try CloudDefender →