Antoine Duno
Founder of ZeriFlow · 10 years fullstack engineering · About the author
Key Takeaways
- A subdomain takeover lets an attacker serve content from your domain by claiming an abandoned cloud resource. This guide explains the mechanics, shows real-world examples, and gives you a concrete remediation and monitoring workflow.
- Includes copy-paste code examples and step-by-step instructions.
- Free automated scan available to verify your implementation.
Subdomain Takeover: What It Is and How to Prevent It
Subdomain takeover is one of the most underestimated vulnerabilities in web security. It requires no code exploit, no brute-forcing, and no sophisticated tooling. An attacker finds a subdomain that points to a cloud resource nobody owns anymore, registers that resource, and immediately begins serving content from your domain. Victims see a fully trusted URL — staging.yourcompany.com, blog.yourcompany.com, api-old.yourcompany.com — that is now under attacker control.
The consequences range from phishing campaigns to cookie theft (depending on cookie scope) to reputational damage. HackerOne''s bug bounty data consistently lists subdomain takeovers as high-severity findings, with payouts frequently in the $1,000-$5,000 range — which means attackers are actively hunting for them on your domains right now.
The Mechanics of Subdomain Takeover
The vulnerability requires three conditions:
- 1You have a DNS record (typically a
CNAME) pointingsubdomain.yourdomain.comto an external service endpoint. - 2The external service resource (a GitHub Pages repo, Heroku app, S3 bucket, etc.) no longer exists or is unclaimed.
- 3The external platform allows anyone to register that same endpoint name.
The attack flow:
DNS lookup: staging.yourcompany.com
→ CNAME → yourcompany.github.io
→ yourcompany.github.io resolves to GitHub Pages IPs
→ GitHub: "No repository configured for yourcompany.github.io" → 404
Attacker creates GitHub account "yourcompany", creates a repository "yourcompany.github.io"
→ GitHub Pages now serves their content at yourcompany.github.io
→ staging.yourcompany.com now serves attacker contentThe attacker does not need access to your DNS — they only need access to the external platform, which is often free and open to registration.
Real-World Examples by Platform
GitHub Pages
GitHub Pages is the most commonly exploited vector. When a company shuts down a project or renames a GitHub organization, CNAME records pointing to orgname.github.io become vulnerable.
Indicators:
$ dig staging.example.com CNAME
staging.example.com. 300 IN CNAME example-staging.github.io.
$ curl -I https://example-staging.github.io
HTTP/2 404
# Response body: "There isn''t a GitHub Pages site here."If you see this 404 from GitHub, the subdomain is likely claimable.
Heroku
Heroku apps have URLs like appname.herokuapp.com. When an app is deleted but the DNS CNAME pointing to it remains, anyone can create a new Heroku app with the same name:
subdomain.example.com → CNAME → example-prod-api.herokuapp.com
# App was deleted during a migration
# Attacker:
heroku create example-prod-api
# Now controls subdomain.example.comAmazon S3
S3 static website hosting uses bucket names that must match the subdomain. If assets.example.com points to assets.example.com.s3-website-us-east-1.amazonaws.com and the bucket is deleted, any AWS user can create a bucket named assets.example.com:
assets.example.com → CNAME → assets.example.com.s3-website-us-east-1.amazonaws.com
# Bucket deleted
$ curl https://assets.example.com.s3-website-us-east-1.amazonaws.com
# Response: NoSuchBucket
# Attacker creates bucket named "assets.example.com" with public website hosting
# Now serves malicious JS files from assets.example.comThis is particularly dangerous for subdomains serving JavaScript files — the attacker can inject malicious scripts into every page that loads assets from that URL.
Other Vulnerable Platforms
| Platform | Indicator | Risk Level |
|---|---|---|
| Azure App Service | azurewebsites.net endpoint returns error | High |
| Fastly | fastly.net CNAME, Fastly returns 500/503 | High |
| Netlify | netlify.app returns 404 | High |
| Ghost (Pro) | ghost.io CNAME, domain not claimed | Medium |
| Shopify | myshopify.com CNAME, shop deactivated | Medium |
| Zendesk | zendesk.com CNAME, help center removed | Medium |
| Tumblr | tumblr.com CNAME, blog deleted | Medium |
A comprehensive list of vulnerable services and their fingerprints is maintained at can-i-take-over-xyz.
How Attackers Exploit a Taken-Over Subdomain
Once an attacker controls a subdomain, several attack vectors open:
Phishing — The attacker creates a convincing login page at accounts.yourcompany.com or login.yourcompany.com. Users trust the domain. Password reuse and credential harvesting follow.
Cookie theft — Cookies set with Domain=.yourcompany.com (note the leading dot) are sent by the browser to all subdomains including the taken-over one. If your application sets session cookies this way, the attacker can harvest them.
XSS via asset injection — An attacker who controls static.yourcompany.com can serve malicious JavaScript that any page loading resources from that subdomain will execute.
CORS abuse — If your API allowlists *.yourcompany.com in its CORS policy, the attacker''s origin becomes a trusted CORS origin, enabling cross-origin requests to your API with victim credentials.
Email spoofing — Subdomain control can sometimes be used to pass SPF/DKIM checks if mail infrastructure uses that subdomain, enabling email spoofing from a trusted-looking address.
Tools to Scan for Vulnerable Subdomains
Subzy
# Install
go install github.com/PentestPad/subzy@latest
# Scan a list of subdomains
subzy run --targets subdomains.txt
# Output vulnerable subdomains only
subzy run --targets subdomains.txt --vulnNuclei with Subdomain Takeover Templates
# Install nuclei
go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Run subdomain takeover templates against a list
nuclei -l subdomains.txt -t ~/nuclei-templates/takeovers/
# Against a single target
nuclei -u https://staging.example.com -t ~/nuclei-templates/takeovers/Subjack
go install github.com/haccer/subjack@latest
subjack -w subdomains.txt -t 100 -timeout 30 -ssl -c ~/go/pkg/mod/github.com/haccer/subjack@latest/fingerprints.jsonEnumerating Your Subdomains First
Before you can scan for takeovers, you need a comprehensive list of your subdomains:
# subfinder
go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
subfinder -d yourcompany.com -o subdomains.txt
# amass
go install github.com/owasp-amass/amass/v4/...@master
amass enum -d yourcompany.com -o subdomains.txt
# Certificate Transparency logs (free, no install needed)
curl "https://crt.sh/?q=%.yourcompany.com&output=json" | jq ''.[].name_value'' | sort -uHow to Fix Vulnerable Subdomains
The fix depends on whether you still need the subdomain:
If You No Longer Need the Subdomain
Delete the DNS record. This is the correct fix and the only permanent one:
# Via AWS Route 53 CLI
aws route53 change-resource-record-sets \\
--hosted-zone-id YOUR_ZONE_ID \\
--change-batch ''{
"Changes": [{
"Action": "DELETE",
"ResourceRecordSet": {
"Name": "staging.yourcompany.com",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{"Value": "yourcompany-staging.herokuapp.com"}]
}
}]
}''If You Still Need the Subdomain
Re-create the resource at the cloud provider before deleting and recreating the DNS record:
- 1Create a new Heroku app / GitHub Pages repo / S3 bucket with the same endpoint name
- 2Verify it returns a valid 200 response
- 3Update or confirm the DNS record is pointing to it correctly
GitHub Pages — Verify Your Custom Domain
GitHub provides domain verification that prevents others from claiming your organization''s domain:
- 1Go to your GitHub organization Settings → Pages
- 2Add and verify your apex domain (e.g.,
yourcompany.com) - 3GitHub will refuse to serve GitHub Pages content for your verified domain from any repository you do not own
Preventing Future Subdomain Takeovers
Maintain a DNS inventory — keep a record of every DNS record and the resource it points to. When you decommission a cloud resource, the DNS record deletion should be part of the offboarding checklist.
Automate DNS record cleanup — use infrastructure-as-code (Terraform, Pulumi) where DNS records are created and destroyed together with their associated cloud resources. If the resource is deleted, the DNS record is deleted in the same terraform destroy.
# Terraform — DNS record tied to resource lifecycle
resource "heroku_app" "staging" {
name = "yourcompany-staging"
region = "us"
}
resource "aws_route53_record" "staging" {
zone_id = var.hosted_zone_id
name = "staging.yourcompany.com"
type = "CNAME"
ttl = 300
records = ["${heroku_app.staging.web_url}"]
}
# When heroku_app.staging is destroyed, aws_route53_record.staging goes with itUse short TTLs on CNAME records — a TTL of 300 seconds (5 minutes) means if a DNS record is compromised, propagation of the fix is fast.
Monitor for DNS changes — set up alerts when DNS records change unexpectedly. ZeriFlow monitors your DNS configuration continuously and alerts you via Slack, Discord, or email when records change or when new vulnerability indicators appear.
Quarterly subdomain audits — run your subdomain enumeration and takeover scan every quarter. Cloud infrastructure changes frequently, and a record that was safe six months ago may now point to a deleted resource.
What to Do If You''ve Already Been Taken Over
- 1Immediately delete the CNAME DNS record to break the takeover
- 2Rotate any cookies that may have been harvested — force logout all active sessions
- 3If the subdomain was serving JavaScript or other assets, audit your CSP to determine which pages loaded content from it
- 4Notify affected users if session theft is possible
- 5File an abuse report with the platform (GitHub, Heroku, etc.) to have the attacker''s account removed
- 6Conduct a full DNS audit to identify any other at-risk records
ZeriFlow scans your DNS records as part of its 80+ security checks, identifying dangling CNAMEs and subdomain configurations that suggest takeover risk. Run a free scan at zeriflow.com and see your full DNS security picture in under 60 seconds.