Subdomain Takeover: The Silent Attack That Hijacks Your Brand (and How to Stop It)
It starts with a CNAME record that was never cleaned up.
Six months ago, your team spun up a marketing landing page on a Heroku dyno, pointed campaign.yourdomain.com at myapp-marketing.herokuapp.com, and ran the campaign. When the campaign ended, you deleted the Heroku app. But no one deleted the CNAME record.
Now myapp-marketing.herokuapp.com is available for anyone to register. Someone registers it, sets up a fake page that looks like your login screen, and suddenly campaign.yourdomain.com — a subdomain carrying your trusted domain's cookies and certificate — is serving credentials-harvesting content.
This is a subdomain takeover. It is not theoretical. Uber, Shopify, GitHub, and dozens of major companies have had subdomain takeovers reported against them through bug bounty programs. The fix is straightforward once you know where to look. The danger is that most teams do not know where to look.
How Subdomain Takeovers Work
sequenceDiagram
participant DNS as Your DNS
participant CNAME as CNAME Target
participant Attacker as Attacker
Note over DNS,CNAME: 6 months ago
DNS->>CNAME: campaign.yourdomain.com → stale.herokuapp.com
Note over CNAME: App was deleted, but CNAME remains
Note over DNS,CNAME: Today
Attacker->>CNAME: Register stale.herokuapp.com
CNAME-->>Attacker: Ownership = Attacker
DNS->>CNAME: Users visit campaign.yourdomain.com
CNAME-->>Attacker: Attacker's content served under yourdomain.com
The attack succeeds because:
- Your DNS still points to the provider's namespace
- The specific target (Heroku slug name, S3 bucket, GitHub Pages repo, etc.) is now unclaimed
- The attacker claims it at the provider level
- Your DNS resolves correctly to their content
The Most Vulnerable Service Categories
Not all CNAME targets are equally vulnerable. The risk depends on whether the provider allows unclaimed names to be registered by new users:
| Service | Takeover Risk | Notes |
|---|---|---|
| Heroku | 🔴 High | App names can be recycled and registered |
| GitHub Pages | 🔴 High | Deleted repos/users can be reclaimed |
| Amazon S3 | 🔴 High | Bucket names are global namespace |
| AWS CloudFront | 🟡 Medium | Requires specific distribution IDs |
| Netlify | 🔴 High | Site names easily re-claimable |
| Vercel | 🟡 Medium | Better protections, but still possible |
| Azure Blob | 🔴 High | Storage account names are global |
| Fastly | 🟡 Medium | Requires specific configuration |
| Custom VPS | 🟢 Low | IP can be reassigned, harder to exploit |
Step 1: Enumerate Your Attack Surface
You cannot protect subdomains you do not know exist. Start with a full enumeration:
# Using subfinder for passive DNS enumeration
subfinder -d yourdomain.com -all -o subdomains.txt
# Using crt.sh for certificate transparency log mining
curl -s "https://crt.sh/?q=%25.yourdomain.com&output=json" | \
jq -r '.[].name_value' | sort -u > ct-subdomains.txt
# Combine and deduplicate
cat subdomains.txt ct-subdomains.txt | sort -u > all-subdomains.txt
echo "Total subdomains found: $(wc -l < all-subdomains.txt)"
For most companies, this enumeration reveals far more subdomains than the team was aware of — legacy staging environments, old campaign pages, forgotten API subdomains.
Step 2: Check for Dangling CNAME Records
A dangling CNAME is a DNS record pointing to a target that no longer exists or responds:
#!/bin/bash
# check-dangling-cnames.sh
while IFS= read -r subdomain; do
# Get CNAME target
cname=$(dig CNAME "$subdomain" +short)
if [ -n "$cname" ]; then
# Check if the CNAME target responds
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "https://$subdomain")
if [ "$http_code" = "000" ] || [ "$http_code" = "404" ]; then
echo "⚠️ POTENTIAL DANGLING CNAME: $subdomain → $cname (HTTP: $http_code)"
fi
fi
done < all-subdomains.txt
Step 3: Automated Detection with Cross-Reference
More sophisticated detection cross-references your CNAME targets against known vulnerable service fingerprints:
// scripts/check-subdomain-takeover.ts
interface TakeoverCandidate {
subdomain: string;
cname: string;
service: string;
fingerprint: string;
severity: 'critical' | 'high' | 'medium';
}
// Fingerprints of vulnerable provider error pages
const TAKEOVER_FINGERPRINTS = [
{
service: 'Heroku',
cnamePattern: /\.herokuapp\.com$/,
errorFingerprint: 'There is no app configured at that hostname',
severity: 'critical' as const,
},
{
service: 'GitHub Pages',
cnamePattern: /\.github\.io$/,
errorFingerprint: "There isn't a GitHub Pages site here",
severity: 'critical' as const,
},
{
service: 'Netlify',
cnamePattern: /\.netlify\.app$/,
errorFingerprint: 'Not Found - Request ID',
severity: 'critical' as const,
},
{
service: 'Amazon S3',
cnamePattern: /\.s3\.amazonaws\.com$/,
errorFingerprint: 'NoSuchBucket',
severity: 'high' as const,
},
{
service: 'Zendesk',
cnamePattern: /\.zendesk\.com$/,
errorFingerprint: 'Help Center Closed',
severity: 'high' as const,
},
];
async function checkForTakeover(subdomain: string): Promise<TakeoverCandidate | null> {
const cname = await resolveCNAME(subdomain);
if (!cname) return null;
for (const fp of TAKEOVER_FINGERPRINTS) {
if (!fp.cnamePattern.test(cname)) continue;
try {
const response = await fetch(`https://${subdomain}`, {
signal: AbortSignal.timeout(10_000),
});
const body = await response.text();
if (body.includes(fp.errorFingerprint)) {
return {
subdomain,
cname,
service: fp.service,
fingerprint: fp.errorFingerprint,
severity: fp.severity,
};
}
} catch (error) {
// Connection refused / DNS NXDOMAIN can also indicate dangling record
}
}
return null;
}
Step 4: CI/CD Integration
Run subdomain takeover checks as part of your security CI pipeline:
# .github/workflows/subdomain-security.yml
name: Subdomain Takeover Check
on:
schedule:
- cron: '0 6 * * *' # Daily at 6 AM
push:
paths:
- 'infra/dns/**' # Also run when DNS config changes
jobs:
subdomain-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install subfinder
run: |
go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
- name: Enumerate subdomains
run: |
subfinder -d ${{ vars.DOMAIN }} -all -o subdomains.txt
echo "Found $(wc -l < subdomains.txt) subdomains"
- name: Check for takeover candidates
run: npx ts-node scripts/check-subdomain-takeover.ts subdomains.txt
- name: Alert on findings
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: '🚨 CRITICAL: Subdomain takeover candidates detected!'
channel: '#security-alerts'
Step 5: Immediate Remediation When Found
If you discover a vulnerable subdomain, act immediately:
PRIORITY 1 (do in the next hour):
→ Add a placeholder record at the CNAME target if possible
→ Remove the dangling CNAME record from DNS
PRIORITY 2 (do in the next day):
→ Audit ALL CNAME records and clean up stale ones
→ Check for cookies with Domain=.yourdomain.com that could be stolen
→ Rotate any session tokens that could have been captured
PRIORITY 3 (do this week):
→ Implement DNS record lifecycle process (cleanup on service deprovisioning)
→ Add subdomain enumeration to your CI/CD pipeline
→ Consider using DNS CAA records to restrict certificate issuance
Preventing Future Takeovers: Process Changes
The technical fix (removing dangling CNAMEs) is only half the solution. The process fix prevents new ones from appearing:
flowchart LR
A[Provision new subdomain] --> B[Document in DNS registry]
B --> C[Deploy service]
C --> D[Service lifecycle]
D --> E{Service\nbeing retired?}
E -->|Yes| F[Decommission checklist]
F --> G[Remove CNAME record FIRST]
G --> H[Then delete cloud resource]
H --> I[Update DNS registry]
E -->|No| D
The key insight: always remove the DNS record before removing the cloud resource. In the reverse order, there is a window of vulnerability between resource deletion and DNS cleanup.
Monitoring Your Subdomains with ScanlyApp
ScanlyApp's continuous scan capability can be configured to monitor your public-facing subdomains for unexpected content changes — including the signature error pages that indicate a takeover has occurred or is imminent.
By scheduling scans against your subdomain inventory, you get an early warning system: if legacy.yourdomain.com suddenly starts returning a GitHub Pages "site not found" page instead of its expected content, you will be alerted before attackers capitalize on it.
Set up subdomain monitoring today: Try ScanlyApp free and schedule scans against your subdomain inventory to detect content changes and takeover indicators.
Related articles: Also see a complete web security testing foundation to build on, automating dynamic security scans alongside subdomain monitoring, and other critical misconfigurations that leave your site exposed.
Summary: The Subdomain Takeover Defense Matrix
| Layer | Action | Frequency |
|---|---|---|
| Enumeration | Run subfinder + crt.sh | Weekly |
| Fingerprint check | Check error pages against known patterns | Daily |
| DNS audit | Review all CNAME records | Monthly |
| Process | Cleanup checklist on deprovisioning | Every time |
| Monitoring | Automated scan of subdomain inventory | Daily |
| Alerting | Slack/PagerDuty on detection | Real-time |
Subdomain takeovers are entirely preventable. The combination of regular enumeration, automated fingerprint checking, and a disciplined deprovisioning process eliminates the risk in practice. The question is simply whether you invest 4 hours to build the monitoring, or whether you discover the vulnerability through a bug bounty report — or worse, a security incident.
Further Reading
- can-i-take-over-xyz: The community-maintained list of services vulnerable to subdomain takeover, with fingerprint patterns and remediation status
- HackerOne — Subdomain Takeover Explained: Practical guide from HackerOne covering how attackers exploit subdomain takeovers and how to prevent them
- OWASP Testing Guide — Subdomain/DNS Misconfiguration: OWASP's methodology for testing subdomain takeover vulnerabilities in configuration audits
- Subfinder — Subdomain Enumeration Tool: The open-source passive subdomain discovery tool used as the basis for the enumeration scripts in this guide
Monitor your subdomains continuously for takeover risk: Try ScanlyApp free and schedule automated subdomain health scans that alert you before misconfigurations become exploits.
