Preventing Subdomain Takeover

Understanding subdomain takeover vulnerabilities, detecting dangling DNS records, and implementing prevention strategies.

What is Subdomain Takeover?

Subdomain takeover occurs when a subdomain points to an external service that has been decommissioned or never properly configured. An attacker can claim the service and serve malicious content from your subdomain.

This vulnerability is dangerous because attackers inherit your domain's reputation, can bypass same-origin policies to steal cookies, and can serve phishing content that appears legitimate.

How Subdomain Takeover Works

Before decommissioning:
blog.company.com ──CNAME──▶ company.wordpress.com ──▶ WordPress Server

After decommissioning (vulnerable):
blog.company.com ──CNAME──▶ company.wordpress.com ──▶ "Site Not Found"

After takeover:
blog.company.com ──CNAME──▶ company.wordpress.com ──▶ Attacker's Content
                            (Attacker claims this name)

Dangling DNS Records

Dangling DNS records are the root cause. These occur when DNS records continue pointing to resources that no longer exist:

; Common vulnerable scenarios
staging.company.com    CNAME   old-staging.azurewebsites.net
dev.company.com        CNAME   project-dev.herokuapp.com
shop.company.com       CNAME   company.shopify.com

Cloud Service Fingerprints

Each service has distinctive error pages when unclaimed:

Service Fingerprint Pattern
AWS S3 "NoSuchBucket", "The specified bucket does not exist"
GitHub Pages "There isn't a GitHub Pages site here"
Heroku "No such app", "There's nothing here, yet"
Azure Web Apps "404 Web Site not found"
Shopify "Sorry, this shop is currently unavailable"
Fastly "Fastly error: unknown domain"

Detection Script

import dns.resolver
import requests
 
class SubdomainTakeoverScanner:
    def __init__(self):
        self.fingerprints = {
            'AWS S3': {
                'cnames': ['.s3.amazonaws.com'],
                'patterns': ['NoSuchBucket']
            },
            'GitHub Pages': {
                'cnames': ['.github.io'],
                'patterns': ["There isn't a GitHub Pages site here"]
            },
            'Heroku': {
                'cnames': ['.herokuapp.com'],
                'patterns': ['No such app']
            },
            'Azure': {
                'cnames': ['.azurewebsites.net'],
                'patterns': ['404 Web Site not found']
            }
        }
    
    def resolve_cname(self, subdomain):
        try:
            answers = dns.resolver.resolve(subdomain, 'CNAME')
            return str(answers[0].target).rstrip('.')
        except:
            return None
    
    def check_http(self, subdomain):
        try:
            response = requests.get(f'https://{subdomain}', timeout=10, verify=False)
            return response.text
        except:
            return None
    
    def scan(self, subdomain):
        cname = self.resolve_cname(subdomain)
        if not cname:
            return {'subdomain': subdomain, 'vulnerable': False}
        
        for service, config in self.fingerprints.items():
            for cname_pattern in config['cnames']:
                if cname_pattern in cname.lower():
                    http_body = self.check_http(subdomain) or ''
                    for pattern in config['patterns']:
                        if pattern.lower() in http_body.lower():
                            return {
                                'subdomain': subdomain,
                                'cname': cname,
                                'service': service,
                                'vulnerable': True
                            }
        
        return {'subdomain': subdomain, 'cname': cname, 'vulnerable': False}
 
# Usage
scanner = SubdomainTakeoverScanner()
result = scanner.scan('blog.example.com')
if result['vulnerable']:
    print(f"VULNERABLE: {result['subdomain']} -> {result['cname']}")

Prevention Strategies

1. DNS Record Inventory

Maintain an inventory of all DNS records and their purposes:

subdomains:
  - name: blog.company.com
    type: CNAME
    target: company.wordpress.com
    owner: marketing
    purpose: Company blog
    external_service: WordPress.com

2. Decommissioning Process

## Service Decommissioning Checklist
 
1. [ ] Remove DNS records FIRST
2. [ ] Wait for TTL to expire
3. [ ] Verify DNS propagation
4. [ ] Only then decommission the external service
5. [ ] Update DNS inventory documentation

3. Automated Monitoring

import schedule
 
def monitor_subdomains():
    subdomains = load_from_inventory()
    scanner = SubdomainTakeoverScanner()
    
    for subdomain in subdomains:
        result = scanner.scan(subdomain)
        if result['vulnerable']:
            send_alert(f"VULNERABLE: {subdomain}")
 
schedule.every(24).hours.do(monitor_subdomains)

4. CAA Records

Control which CAs can issue certificates for your domain:

company.com.    IN CAA 0 issue "letsencrypt.org"
company.com.    IN CAA 0 iodef "mailto:security@company.com"

5. Avoid Wildcard DNS

; DANGEROUS: Any subdomain resolves
*.company.com.    IN A    203.0.113.1
 
; SAFER: Explicit records only
www.company.com.  IN A    203.0.113.1
api.company.com.  IN A    203.0.113.2

Impact of Subdomain Takeover

Attack Vector Impact
Cookie theft Session hijacking for all subdomains
Phishing Credential theft with trusted domain
OAuth hijacking Account takeover via callback interception
Malware distribution Trusted domain bypasses security tools

Automated Subdomain Takeover Detection

Subdomain takeover vulnerabilities require ongoing monitoring as your DNS configuration changes and external services are provisioned or decommissioned.

RedVeil's AI-powered penetration testing platform identifies subdomain takeover vulnerabilities by analyzing your DNS records, fingerprinting external services, and testing for claimable resources. The platform provides proof-of-concept evidence and clear remediation guidance.

Scan your subdomains for takeover vulnerabilities with RedVeil →

Ready to run your own test?

Start your first RedVeil pentest in minutes.