Fundamentals
T1525S3 Exposure
S3 exposure happens when bucket permissions or object policies make data accessible beyond the intended audience — how it happens, how to detect it in CloudTrail, and how to respond when you find exposed data.
View on Graph
What It Is and Why It Matters
S3 exposure is one of the most common and damaging cloud security failures because S3 is widely used to store everything from application assets to customer data to internal documents. The total volume of breached records from exposed S3 buckets across public disclosures exceeds 100 million.
The risk comes from the complex interaction between multiple permission layers — bucket policies, IAM user policies, ACLs, and account-level public access blocks — where a single misconfigured setting can make an entire bucket readable by anyone on the internet.
Even when a bucket itself is private, data can still be exposed if an IAM role with read access is compromised or if pre-signed URLs are generated without expiration.
For analysts, S3 exposure incidents demand rapid assessment of what data was accessible, who may have accessed it, and whether any objects were downloaded.
How Public Access Happens — The Four Permission Layers
S3 has four independent permission layers, and a bucket is publicly accessible if any one of them allows public access:
| Layer | What It Controls | Typical Misconfiguration |
|---|---|---|
| Bucket Policy | JSON policy document attached to the bucket | "Principal": "*" with "Effect": "Allow" on "s3:GetObject" — allows anyone on the internet to read objects. |
| Bucket ACL | Legacy access control list (pre-2023 default) | AllUsers or AuthenticatedUsers group granted READ access. |
| Object ACL | Per-object ACL overrides | An individual object set to public even when the bucket is private. |
| Account-level Public Access Block | Account-wide setting that blocks new public policies | This setting is disabled by default. If not enabled, any of the above layers can make data public. |
The most dangerous scenario: an account-level public access block with the setting “Block public and cross-account access if bucket has public policies” set to false. This allows bucket policies to override everything.
Detection — Finding Exposed S3 Buckets
AWS CloudTrail Events
CloudTrail records every S3 API call. The following events are critical for S3 exposure detection:
| Event Name | What It Indicates | Suspicious Patterns |
|---|---|---|
PutBucketPolicy | Bucket policy modified | An S3 bucket that previously had no policy suddenly gets one with "Principal": "*". |
PutBucketAcl | Bucket ACL modified | AllUsers group added with READ access. |
PutObjectAcl | Object ACL modified | Individual objects set to public — a pattern suggesting exfiltration staging. |
GetObject | Object read | Multiple GetObject calls from unfamiliar IPs or across many objects (bulk download). |
ListBucket | Bucket listing | An attacker listing bucket contents to find valuable objects. |
DeleteBucketPolicy | Bucket policy removed | Suspicious if done right after exfiltration — attacker covering tracks. |
PutPublicAccessBlock | Public access block enabled or disabled | Disabling the public access block before making a bucket public is a clear compromise signal. |
CloudTrail KQL Query — Detect Buckets Made Public
// Find when a bucket was made publicly accessible
CloudTrail
| where TimeGenerated > ago(7d)
| where EventName in ("PutBucketPolicy", "PutBucketAcl", "PutObjectAcl")
| extend BucketName = parse_json(RequestParameters).bucketName
| extend PolicyStatement = parse_json(RequestParameters).bucketPolicy
| extend Grantee = parse_json(RequestParameters).AccessControlPolicy.AccessControlList.Grant[*].Grantee
| extend Principal = PolicyStatement.Statement[0].Principal
| extend Effect = PolicyStatement.Statement[0].Effect
| extend Action = PolicyStatement.Statement[0].Action
| where Principal == "*" or Grantee.URI contains "global/AllUsers" or Grantee.URI contains "global/AuthenticatedUsers"
| project TimeGenerated, EventName, BucketName, UserIdentity.arn, SourceIPAddress, PolicyStatement
AWS Config Rules
AWS Config provides managed rules specifically for S3 exposure detection:
| Rule Name | What It Checks |
|---|---|
s3-bucket-public-read-prohibited | Checks buckets are not publicly readable |
s3-bucket-public-write-prohibited | Checks buckets are not publicly writable |
s3-bucket-ssl-requests-only | Checks bucket policies deny HTTP requests |
s3-bucket-logging-enabled | Checks server access logging is enabled |
s3-bucket-server-side-encryption-enabled | Checks SSE is enabled (KMS, AES-256) |
s3-bucket-blacklisted-actions-prohibited | Custom: blocks specific actions like GetObject for * principals |
Enable these rules in AWS Config and configure SNS notifications for NonCompliant evaluations.
S3 Server Access Logs
S3 can log every request to the bucket. Key fields for investigation:
| Field | Example | What It Tells You |
|---|---|---|
bucket | acme-customer-data | Which bucket was accessed |
key | customers/prod/export-2025-01.csv | Which object was accessed |
remoteip | 203.0.113.5 | Who accessed it (correlate to org IP ranges) |
requester | arn:aws:iam::123456789012:user/attacker-access-key | Which IAM identity accessed the bucket |
operation | REST.GET.OBJECT | Read, write, or list operation |
http_status | 200 | Successful access (exfiltration) |
error_code | AccessDenied | Blocked access attempt |
bytes_sent | 8589934592 | Object size — large values confirm exfiltration |
user_agent | aws-cli/2.x | CLI access is more suspicious than browser access. |
Third-Party Monitoring
- Trusted Advisor (AWS) — S3 Bucket Permissions check in the Security category.
- Security Hub — Aggregates Config findings, GuardDuty, and Inspector into a single dashboard with CIS benchmarks for S3.
- CloudSploit / ScoutSuite — Open-source auditing tools that scan AWS accounts for public S3 buckets.
- GrayHatWarfare — Public bucket search engine (buckets.grayhatwarfare.com). Search your org’s naming patterns to see if your buckets appear.
What To Do When You Find Exposed Data
If you discover a publicly accessible S3 bucket, follow this sequence:
- Immediately block public access — Set the account-level public access block to block all public policies and ACLs. This is the fastest way to cut access without modifying individual bucket policies.
- Identify the data — Run
aws s3 ls s3://bucket-name --recursive(using an internal role) to enumerate objects. Create a manifest of file types, sizes, and date ranges. - Check CloudTrail — Query
GetObjectevents for the bucket. Identify which IPs accessed which objects and when. - Notify stakeholders — Legal, compliance, and engineering teams need to know. The bucket owner may need to issue a data breach notification depending on data classification.
- Rotate credentials — If IAM keys were in the exposed bucket, rotate them immediately. API keys, database passwords, and TLS private keys are high-value targets.
- Root cause analysis — Why was the bucket exposed? Misconfigured IaC template? Manual override? Missing public access block? Fix the process, not just the bucket.
Real-World Examples
- Dow Jones watchlist exposure (2017) — An exposed S3 bucket contained the names, addresses, and financial account details of over two million high-risk individuals and politically exposed persons from Dow Jones watchlist data.
- Verizon NICE Systems leak (2017) — A third-party vendor misconfigured an S3 bucket containing Verizon customer data including names, phone numbers, and account PINs for over 6 million subscribers.
- Accenture cloud exposure (2017) — Four unsecured S3 buckets exposed internal Accenture data including API access keys, authentication credentials, and customer decryption keys.
- US Army data leak (2023) — An exposed S3 bucket belonging to a contractor contained US Army intelligence data, accessible to anyone with the bucket URL.
Prevention — S3 Security Checklist
| Item | Implementation |
|---|---|
| Enable account-level public access block | AWS Organizations: apply an SCP that denies s3:PutBucketPolicy and s3:PutBucketAcl on buckets that don’t have the account-level block. |
| Deny HTTP access | Bucket policy: "Condition": {"Bool": {"aws:SecureTransport": "false"}} → "Effect": "Deny". |
| Enable S3 server access logging | Send logs to a separate logging bucket in a different account with write-only access. |
| Enable CloudTrail Data Events for S3 | Capture GetObject, ListBucket, PutObject for all buckets (additional cost, but worth it for sensitive data buckets). |
| Use bucket policies over ACLs | ACLs are harder to audit. Use resource-based policies with IAM conditions and Principal restrictions. |
| Configure pre-signed URL expiration | Pre-signed URLs should expire in minutes, not days or never. |
| Automate compliance scanning | Use AWS Config with managed S3 rules. Send non-compliance alerts to a security Slack channel. |
Related
- Insider Threat — detection and response for T1078 techniques
- Cloud Incident Response — detection and response for T1525, T1526, T1078, T1530 techniques
- Active Directory Basics — covers the active directory basics concepts
- AWS Misconfigurations — detection and response for T1525, T1613 techniques
