Before running a single scanner, a researcher opens bucket-stream, connects to certstream, and within minutes has a list of S3 buckets derived from the target company's subdomains. No packet was sent to the target. No log was written to the SIEM.
Most security teams treat bucket exposure as a configuration problem fixed with a click on "Block Public Access". That assumption misses the point: even with Block Public Access enabled, the discovery phase through CT logs is unaffected -- it runs before any permission check. Attackers treat it as a passive discovery problem, and have already mapped your assets through CT logs and GrayhatWarfare before making the first API call. That difference in perspective is why S3 incidents keep happening even at companies with formal cloud security policies.
Prerequisites: Python 3 + pip (bucket-stream), AWS CLI, gsutil (GCS), az CLI (Azure). Basic knowledge of DNS and cloud storage naming conventions.
CT Logs Are Your Company's Public Bucket Directory
Any TLS certificate issued for company domains -- such as backup.acme.com or cdn.acme.com -- appears in CT logs through certstream. bucket-stream extracts naming components from those domains and generates bucket name permutations to test: acme-backup, backup-acme, cdn-acme. The bucket itself never needs its own certificate; it's the company's operational domains that expose the naming convention.
bucket-stream (eth0izzle) listens to certstream in real time and generates bucket name permutations from domains observed in CT logs. The tool sends no packets to the target during monitoring.
git clone https://github.com/eth0izzle/bucket-stream
pip install -r requirements.txt
python3 bucket-stream.py --only-interesting --skip-lets-encrypt
bucket-stream uses a wordlist of common patterns (company-backup, company-logs, company-data, company-prod, company-staging) -- the same ones development teams adopt by convention. Names that make sense internally are exactly the ones an attacker with certstream access will test first. The --skip-lets-encrypt flag cuts noise by filtering out certificates from personal projects and low-interest infrastructure.
GrayhatWarfare Has Already Indexed 4 Billion Files
buckets.grayhatwarfare.com indexes 318,000 buckets and 4 billion files (numbers from 2022; current volume is larger) from S3, GCS, and Azure Blob, searchable without authentication. A researcher searches by company name, filters by extension (.env, .sql, .key, .pem, .bak), and gets results before running any active scan.
snallybuckster (DanusMinimus) automates GrayhatWarfare searches and downloads files with extensions of interest. Combined with bucket-stream, this forms a complete pipeline: CT logs identify naming patterns, GrayhatWarfare confirms whether sensitive files have already been indexed.
If your company's .env file appears in GrayhatWarfare, the bucket was public at some point -- possibly for hours or days before it was fixed. Auditing what was indexed is not the same as checking current permission state: the data may have been copied before the fix was applied.
S3Scanner and AWSBucketDump: Permission Confirmation After Passive Identification
With the naming pattern identified through CT logs, the active phase is brief. S3Scanner (sa7mon/S3Scanner) tests permissions against a list of bucket names without AWS authentication. AWSBucketDump walks the bucket's files looking for credentials, keys, and configs using a keyword wordlist.
pip install s3scanner
echo "acme-backup
acme-logs
acme-data
acme-prod" > targets.txt
s3scanner scan --buckets-file targets.txt
python AWSBucketDump.py -l targets.txt -g interesting_Keywords.txt -d 1
The volume of logs generated by a permission scan on 20 bucket names is minimal. The name discovery phase through CT logs generated none at all. That asymmetry is the key point: the most valuable part of the attack happened without any traceable evidence.
GCS and Azure: Multi-Cloud Surfaces With Underrated Equivalent Techniques
In GCS, an allUsers binding with roles/storage.objectViewer makes the bucket anonymously listable over HTTP. GCPBucketBrute (Rhino Security Labs) enumerates GCS buckets with target name permutations without authentication. Most pentesters overlook this surface because they focus exclusively on AWS.
# GCS — public access test
curl -s "https://storage.googleapis.com/TARGET-BUCKET/?prefix=" | head -50
# Azure Blob — anonymous container listing
curl -s "https://ACCOUNT.blob.core.windows.net/CONTAINER?restype=container&comp=list"
# GCPBucketBrute
python3 gcpbucketbrute.py -k none -l targets.txt -o output.txt
Azure Blob has three access levels: Private, Blob (anonymous read of individual blobs), and Container (full anonymous listing and read). A container with publicAccess=container returns XML with all blobs via restype=container&comp=list with no credentials.
The critical blind spot in GCS: anonymous user requests with allUsers enabled do not generate Data Access logs by default. The security team reviews IAM logs and finds nothing suspicious, because anonymous access does not appear in those logs.
Accenture, Booz Allen, Time Warner, and GoDaddy: What Was in the Buckets
Accenture (September 2017): Four public S3 buckets containing API keys, VPN keys, an AWS KMS master key, and plaintext passwords. The contents included Accenture Cloud Platform software, the multicloud management product used by Fortune 500 clients. UpGuard discovered it on 09/17/2017 and the fix came within 24 hours of notification.
Booz Allen Hamilton (May 2017): 60,000 files from the NGA project (28 GB), classified Top Secret. SSH keys and plaintext passwords exposed without protection. Chris Vickery discovered it on 05/22/2017; Booz Allen's CISO did not respond for days.
Time Warner (August 2017): Four million customer records (November 2010 through July 2017) via a BroadSoft vendor S3 bucket. Data included usernames, MAC addresses, serial numbers, and account numbers. Discovered by the Kromtech Security Center while investigating an unrelated incident.
GoDaddy (June 2018): Configuration data for 24,000 AWS servers (hostname, OS, workload, region, CPU, memory, pricing) in a bucket created by an AWS employee during a commercial negotiation, named abbottgodaddy. UpGuard discovered it on 06/19/2018; the fix was confirmed on 07/26/2018.
Three of the four incidents involved third parties: BroadSoft, an AWS employee, the Accenture cloud platform. The exposure perimeter does not stop at the company's own AWS account.
Bucket Takeover: The Deleted Name Is Globally Available to Any AWS Account
Beyond CT logs and GrayhatWarfare, there is a third passive discovery surface: residual DNS from deleted buckets.
S3 bucket names are globally unique. A company that deletes a bucket without removing the associated CNAME leaves the legitimate subdomain open to hijacking from any AWS account. The mechanics: CNAME assets.company.com points to old-bucket.s3.amazonaws.com; an attacker registers old-bucket in their own account and now controls the subdomain.
# Detect dangling CNAME
dig CNAME assets.company.com
# Fingerprint deleted bucket
curl -s https://BUCKET-NAME.s3.amazonaws.com | grep NoSuchBucket
# If it returns "NoSuchBucket" = takeover candidate
The 2024-2025 variant scales the impact: buckets referenced in JS files, CDNs, or npm packages are deleted by the original owners and re-registered by attackers. Any user who loads the affected package or script runs code from the attacker's account.
The mitigation is sequential and non-negotiable: delete the CNAME first, wait for TTL to expire, then delete the bucket. The reverse order leaves an exposure window that can be exploited in seconds.
Defense Checklist: Technical Controls Without Passive Monitoring Are Not Enough
AWS: Block Public Access has been enabled by default since April 2023, but buckets created before that need a retroactive audit. An SCP through AWS Organizations denies s3:PutBucketAcl with the condition s3:x-amz-acl=public-read on production accounts.
{
"Effect": "Deny",
"Action": "s3:PutBucketAcl",
"Resource": "*",
"Condition": {
"StringEquals": {"s3:x-amz-acl": ["public-read", "public-read-write"]}
}
}
GCS: Uniform Bucket-Level Access (UBLA) disables object-level ACLs and enforces consistent IAM across the entire bucket. Enabling UBLA retroactively does not revoke existing permissions without an explicit audit of previous bindings.
Azure: allowBlobPublicAccess=false on the storage account blocks all containers beneath it, regardless of individual container settings.
# S3 audit — list ACLs for all buckets in the account
aws s3api list-buckets --query "Buckets[].Name" --output text | xargs -I{} aws s3api get-bucket-acl --bucket {}
# GCS — check UBLA
gsutil uniformbucketlevelaccess get gs://BUCKET-NAME
# Azure — disable public access on the storage account
az storage account update --name ACCOUNT --resource-group RG --allow-blob-public-access false
The most neglected control is monitoring your own CT logs. Running bucket-stream against your company's own domains shows exactly what an attacker sees in real time. Any bucket that appears in that output and is not in the official inventory is unknown infrastructure exposed to the global certstream.
Buckets are not discovered by aggressive scanners. They are discovered passively, before any contact with the company's systems. The team that runs bucket-stream against its own domains finds what an attacker will find. The team that does not runs the same search after an incident.
Top comments (0)