Mass3: Why DNS Lookups Beat HTTP Requests for S3 Bucket Enumeration
Hook
A single HTTP request to check if an S3 bucket exists takes 100-300ms. A DNS query? Less than 10ms. That's not an incremental improvement—it's a complete architectural paradigm shift for reconnaissance at scale.
Context
Security researchers and bug bounty hunters routinely discover sensitive data in misconfigured S3 buckets, but finding those buckets requires validating thousands or millions of potential names generated from subdomain enumeration, permutation attacks, and wordlist expansion. Traditional tools like S3Scanner make HTTP requests to bucket endpoints, checking for 200, 403, or 404 responses to determine existence and permissions. This works, but it's painfully slow when you're validating 100,000 candidate names discovered during reconnaissance.
The bottleneck isn't network bandwidth—it's the overhead of establishing HTTPS connections, performing TLS handshakes, and waiting for full HTTP responses. Each request carries significant latency, and AWS rate limiting kicks in quickly when you parallelize too aggressively from a single IP. Mass3 takes a different approach: it treats bucket validation as a pure existence check problem and solves it with DNS queries against S3 bucket endpoints (bucketname.s3.amazonaws.com). Since DNS queries have minimal overhead and you can distribute them across dozens of public resolvers, this approach achieves order-of-magnitude speed improvements while staying under the radar of AWS rate limits.
Technical Insight
Mass3's architecture is deceptively simple, which is exactly why it works so well. The tool spawns multiple goroutines that consume bucket names from a shared channel, perform DNS lookups against a rotating pool of resolvers, and write results to a CSV file. Here's the core lookup logic:
// Each goroutine runs this loop
for bucketName := range bucketChannel {
// Construct S3 bucket DNS name
dnsName := bucketName + ".s3.amazonaws.com"
// Rotate through resolver list
resolver := resolvers[resolverIndex % len(resolvers)]
resolverIndex++
// Perform DNS lookup
addrs, err := net.LookupHost(dnsName)
if err == nil && len(addrs) > 0 {
// Bucket exists - DNS resolved successfully
writeResult(bucketName, addrs)
}
}
The beauty of DNS-based enumeration is that AWS must maintain valid DNS records for all existing S3 buckets—it's a fundamental requirement for the S3 service to function. When you query example-bucket.s3.amazonaws.com and get back an IP address, you've confirmed that bucket exists without ever making an HTTP request or authenticating with AWS. Non-existent buckets return NXDOMAIN (name doesn't exist), making false positives rare when using quality resolvers.
The resolver pool is critical to Mass3's performance. Instead of hammering a single DNS server (which would quickly rate limit you), the tool accepts a file containing dozens or hundreds of public DNS resolvers:
8.8.8.8
1.1.1.1
9.9.9.9
208.67.222.222
# ... hundreds more
Each goroutine round-robins through this list, distributing queries across the entire resolver pool. This has two advantages: first, you parallelize across infrastructure you don't control, making rate limiting nearly impossible to enforce against you. Second, geographic diversity in resolver selection can sometimes bypass regional rate limits or DNS filtering.
The thread count is configurable (typically 50-200 threads), and since DNS queries are I/O-bound rather than CPU-bound, Go's goroutines handle this concurrency elegantly. You're essentially trading memory (goroutine stacks) for speed, and with modern machines having gigabytes of RAM, spawning 200 goroutines is trivial.
Mass3 outputs discovered buckets to a CSV file with basic information: bucket name, resolved IP addresses, and timestamp. This minimalist approach reflects the tool's design philosophy—it does exactly one thing (validate bucket existence via DNS) and does it extremely fast. The expectation is that you'll pipe results into other tools for permissions testing, content enumeration, or vulnerability assessment. This Unix philosophy of composable tools means Mass3 integrates seamlessly into larger reconnaissance pipelines:
# Generate candidate names from subdomains
cat subdomains.txt | sed 's/\./-/g' > candidates.txt
# Validate with Mass3 (extremely fast)
./mass3 -input candidates.txt -resolvers resolvers.txt -threads 150
# Check permissions on discovered buckets
cat results.csv | cut -d',' -f1 | s3scanner --scan
One nuance worth understanding: Mass3 queries the global S3 endpoint (s3.amazonaws.com) by default, but S3 buckets can be region-specific. The DNS resolution still works because AWS maintains CNAME records that redirect to regional endpoints, but you might see different IP addresses depending on resolver location and AWS's DNS routing policies. This doesn't affect existence validation, but it's worth noting if you're trying to determine bucket regions from the IP addresses returned.
Gotcha
The DNS-based approach has a critical weakness: it only confirms bucket existence, not ownership or permissions. A bucket that resolves via DNS might be owned by someone else who claimed that name first, or it could be completely locked down with no public access. Mass3 will happily report it as "discovered," but without follow-up HTTP requests to check bucket permissions, you don't know if it's actually accessible or relevant to your target.
Resolver quality matters enormously. Public DNS resolvers have varying levels of caching, filtering, and reliability. Some resolvers cache NXDOMAIN responses aggressively, leading to false negatives where recently-created buckets don't resolve because the resolver's cache hasn't expired. Other resolvers implement filtering that blocks certain domains or returns synthetic responses. The Mass3 repository references a "fresh.sh" script for cleaning up resolver lists, and you absolutely need this step—using untested resolver lists will pollute your results with false positives that waste time during follow-up analysis. Budget 30-60 minutes to test and curate a quality resolver list before running large-scale enumeration jobs, or expect to spend that time triaging garbage results instead.
Verdict
Use Mass3 if: you've already performed subdomain enumeration, DNS permutation attacks, or wordlist generation and have a large list (10,000+ candidates) of potential S3 bucket names that need rapid validation. It excels in reconnaissance pipelines where speed matters more than detailed information, and you're planning to pipe results into specialized tools for permissions testing and content analysis. The DNS-based approach is perfect when you're validating hypothetical bucket names and need to quickly separate real buckets from the 99% that don't exist.
Skip Mass3 if: you're starting from scratch without existing reconnaissance data (it doesn't generate bucket names), you need comprehensive security assessment including permissions and public access checks (use S3Scanner or AWS security tools instead), or you're working with small lists under 1,000 candidates where the speed advantage doesn't justify adding another tool to your workflow. Also skip it if you can't invest time curating a quality resolver list—bad resolvers will cause more problems than they solve.