
What is a subdomain takeover and how to prevent dangling DNS records?
A subdomain takeover happens when one of your DNS records still points at an external resource, such as a cloud storage bucket, a hosting app or a load balancer, that you've deleted or never fully claimed, and someone else is able to claim that resource and serve their own content on your subdomain. The record that makes it possible is called a dangling DNS record. You prevent takeovers by removing DNS records before you delete the resources they point to, keeping an inventory of which records point where, auditing your zones regularly for targets that no longer exist, and using the domain verification features that cloud platforms provide.
It's one of the most common findings in security assessments, because it doesn't need any vulnerability in your code. It's simply a gap between two systems, your DNS and a third-party platform, that drift out of step over time. In this article you'll learn how dangling records arise, which kinds of records and services are affected, why a takeover is more serious than it first looks, and how to audit your own zones and build processes that stop dangling records appearing at all.
How a Dangling Record Arises
The usual story goes like this:
- A team creates a resource on a cloud platform, for example a static site that the platform serves at
promo-2025.hostingplatform.example. - They add a CNAME so the site appears on their own domain.
- The campaign ends. Someone deletes the site on the platform to save money or tidy up.
- Nobody removes the CNAME. It still points at
promo-2025.hostingplatform.example, which no longer belongs to anyone.
The record left behind looks like this:
promo.example.com. 3600 IN CNAME promo-2025.hostingplatform.example.
At this point, promo.example.com is dangling. On many platforms, resource names are first come, first served. If the platform lets any customer create a resource with that freed-up name and attach the custom hostname promo.example.com to it, without proving they control the domain, then whoever does so gains control of what your subdomain shows.
Which Records Can Dangle
CNAMEs are the most common culprit, but they're not the only one.
| Record type | How it dangles | Example |
|---|---|---|
| CNAME | Points to a hostname for a deleted platform resource | A static site, app service or storage endpoint that was removed |
| A / AAAA | Points to a cloud IP address that has been released | A public IP returned to the provider's pool after a server was shut down |
| NS | Delegates a subdomain to a DNS zone that no longer exists | A hosted zone deleted at a cloud DNS provider while the delegation remains |
| MX | Points to a mail service you no longer use | A mailbox provider account was closed but the records remain |
Dangling CNAMEs
These are the classic case. The record points to a name on a shared platform, such as a storage service, a static hosting service, a platform-as-a-service app, or a CDN. If the platform's resource names can be reused by other customers, and the platform doesn't require domain verification, a takeover is possible.
Dangling IP Addresses
Cloud providers recycle public IP addresses. If an A record points to an elastic or public IP you released, the provider may eventually assign that address to another customer. Their server then receives traffic meant for your subdomain. This is harder to exploit deliberately, because the new owner can't usually choose which IP they get, but it still leaves your subdomain under someone else's control.
Dangling NS Delegations
If you delegate dev.example.com to a hosted zone at a cloud DNS provider and later delete that zone, the delegation still points at the provider's nameservers. On some providers, another customer could create a zone for dev.example.com and, if it lands on the same nameservers, answer for it. This is particularly serious, because it hands over every name under that subdomain, not just one.
Why a Takeover Is Serious
A taken-over subdomain looks completely legitimate to users and browsers, because it really is your domain. Someone controlling it could:
- Host phishing pages on a trusted domain, which also defeats users who have been trained to "check the address bar".
- Obtain a valid TLS certificate for the subdomain, because they can pass domain validation by serving content on it.
- Read cookies scoped to the parent domain. If your application sets cookies for
.example.com, a page on any subdomain can receive them, which may include session cookies. - Bypass security policies such as Content Security Policy or CORS allowlists that trust
*.example.com. - Damage your reputation with spam, malware or offensive content on your domain.
How to Find Dangling Records
The goal is to find every record that points outside your direct control and check that the target still exists and still belongs to you.
Step 1: Get a Full List of Records
Start with a complete export of each zone from your DNS provider. Don't rely on memory or a spreadsheet. Most providers offer a BIND-format export, and the major cloud providers let you list records through their CLIs. For example, with Route 53:
aws route53 list-resource-record-sets \
--hosted-zone-id Z0123456789EXAMPLE \
--query "ResourceRecordSets[?Type=='CNAME'].[Name, ResourceRecords[0].Value]" \
--output text
promo.example.com. promo-2025.hostingplatform.example
docs.example.com. example.docs-host.example
assets.example.com. d111111abcdef8.cloudfront.net
Step 2: Check Whether CNAME Targets Still Resolve
A CNAME whose target returns NXDOMAIN is an immediate red flag. Check it with dig:
dig promo.example.com A
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 40221
;; ANSWER SECTION:
promo.example.com. 3600 IN CNAME promo-2025.hostingplatform.example.
The answer contains your CNAME, but the status is NXDOMAIN, meaning the target name no longer exists. That record is dangling.
Not every dangling record produces NXDOMAIN. Some platforms resolve every name under their domain, then show a "no such site" page. So a target that resolves isn't automatically safe, which is why the inventory step below matters.
Step 3: Automate the Check
This Python script reads a list of names, follows each CNAME, and reports targets that don't resolve. It uses dnspython:
#!/usr/bin/env python3
"""Report CNAME records whose targets no longer resolve."""
import sys
import dns.exception
import dns.resolver
resolver = dns.resolver.Resolver()
resolver.lifetime = 5.0
def cname_target(name):
try:
answer = resolver.resolve(name, "CNAME")
return answer[0].target.to_text()
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
return None
def resolves(name):
for rtype in ("A", "AAAA"):
try:
resolver.resolve(name, rtype)
return True
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
continue
return False
with open(sys.argv[1]) as f:
names = [line.strip() for line in f if line.strip() and not line.startswith("#")]
for name in names:
try:
target = cname_target(name)
if target is None:
continue
if not resolves(target):
print(f"DANGLING? {name} -> {target} (target does not resolve)")
except dns.exception.DNSException as exc:
print(f"ERROR {name}: {exc}")
pip install dnspython
python3 find_dangling.py subdomains.txt
DANGLING? promo.example.com -> promo-2025.hostingplatform.example. (target does not resolve)
Run it on a schedule against an up-to-date export of your zones, and alert on any new finding.
Step 4: Check Delegated Subdomains
For NS delegations, ask each delegated nameserver whether it actually answers for the subdomain:
for ns in $(dig dev.example.com NS +short); do
printf '%-36s' "$ns"
dig @"$ns" dev.example.com SOA +norecurse | grep -oE 'status: [A-Z]+'
done
ns-1234.awsdns-26.org. status: REFUSED
ns-567.awsdns-07.net. status: REFUSED
REFUSED or SERVFAIL from every delegated server means the zone they're supposed to host no longer exists there. Remove the delegation, or recreate the zone, straight away.
Step 5: Check A Records Against Your Inventory
For A and AAAA records pointing at cloud IP ranges, compare them with the addresses actually allocated to your accounts. On AWS, for example:
aws ec2 describe-addresses --query 'Addresses[].PublicIp' --output text | tr '\t' '\n' | sort > owned.txt
while read -r name; do
dig +short "$name" A
done < names.txt | grep -E '^[0-9.]+$' | sort -u > published.txt
comm -13 owned.txt published.txt
The loop looks up every name in names.txt, and the grep keeps only IPv4 addresses, dropping any CNAME targets that dig +short prints along the way. The comm command prints addresses that are published in your DNS but not in your list of owned IPs. Some will be legitimate, such as addresses from other providers or load balancers, but each one should be explained.
How to Prevent Dangling Records
Detection catches problems after they happen. Prevention is about process.
Remove DNS First, Then Delete the Resource
Make it a rule: when you decommission anything, remove the DNS record first, wait for its TTL, and only then delete the cloud resource. That order means there's never a moment when your record points at something you don't own.
Build this into your runbooks and change templates, and into automation if you have it.
Manage DNS and Resources Together
The most reliable fix is to create DNS records in the same code as the resources they point to. With Terraform, a record that references a resource is destroyed along with it:
resource "aws_s3_bucket" "promo" {
bucket = "promo.example.com"
}
resource "aws_s3_bucket_website_configuration" "promo" {
bucket = aws_s3_bucket.promo.id
index_document {
suffix = "index.html"
}
}
resource "aws_route53_record" "promo" {
zone_id = var.zone_id
name = "promo.example.com"
type = "CNAME"
ttl = 300
records = [aws_s3_bucket_website_configuration.promo.website_endpoint]
}
Because the record depends on the bucket's website configuration, running terraform destroy on this module removes the record as well. Terraform also destroys dependent resources before the ones they depend on, so the DNS record goes before the bucket.
Use Platform Domain Verification
Many platforms now protect against takeovers by requiring proof that you control a domain before letting anyone attach it. Use these features wherever they exist:
- GitHub Pages lets you verify a domain for your account or organisation, which prevents other users from using it with their Pages sites.
- Azure App Service supports a TXT record at
asuid.<subdomain>containing a domain verification ID. Azure checks it when a custom domain is added, so another subscription can't simply reuse your hostname. - Other platforms use a TXT record or an HTTP challenge for the same purpose.
Verification records should stay in place for as long as you use the platform.
Keep an Inventory
Maintain a list of every record that points to an external service, along with the owner and the purpose. A simple table is enough to start:
| Name | Type | Target | Service | Owner |
|---|---|---|---|---|
docs.example.com | CNAME | example.docs-host.example | Documentation host | Product team |
assets.example.com | CNAME | d111111abcdef8.cloudfront.net | CDN | Platform team |
dev.example.com | NS | Cloud DNS zone | Development environment | Engineering |
When a service is retired, the inventory tells you exactly which records go with it.
Limit Who Can Create Records
Fewer people creating ad hoc records means fewer forgotten ones. Restrict DNS write access, use pull requests for DNS changes, and require an owner field for new records.
Reduce the Impact If a Takeover Happens
Defence in depth limits what a taken-over subdomain can do:
- Scope cookies to the exact host, such as
www.example.com, rather than the whole domain, and use the__Host-cookie prefix where you can. - Avoid wildcard trust in CORS policies and Content Security Policy.
- Use CAA records to restrict certificate issuance to the CAs you use. This doesn't stop a takeover, but it narrows the options.
- Monitor Certificate Transparency logs for certificates issued for your subdomains that you didn't request.
What to Do If You Find a Dangling Record
- Remove the record immediately, or point it back at a resource you control.
- Check whether it's already been claimed. Visit the subdomain and look for content you don't recognise, and search Certificate Transparency logs for recent certificates on that name.
- If it was taken over, remove the DNS record, report the abuse to the platform, revoke any certificates issued to the attacker, and review whether cookies or sessions on the parent domain could have been exposed.
- Find out how it happened, and fix the decommissioning process that left it behind.
FAQ: Subdomain Takeover
A dangling DNS record is one that points to a resource you no longer control, such as a deleted cloud app, a released IP address or a removed hosted zone. It leaves your subdomain pointing at something another person might be able to claim.
CNAME records are the most common cause, but A and AAAA records pointing to released cloud IPs, NS records delegating to deleted zones, and MX records for closed mail services can all leave subdomains exposed.
Yes. Whoever controls the content on the subdomain can usually pass a certificate authority's domain validation, so the site can show a valid padlock. That's one reason takeovers are so effective for phishing.
Run automated checks at least weekly, and every time you decommission a service. Continuous monitoring is better for large organisations with many teams creating records.
No. DNSSEC proves your records are genuine, but in a takeover your genuine record is the problem, because it points at a resource someone else now controls.
Remove the DNS record first, wait for its TTL to expire, then delete the cloud resource. That way your subdomain never points at something you don't own.
Conclusion
A subdomain takeover isn't a bug in your application. It's a gap between your DNS and a platform you've stopped using, where a record keeps pointing at a resource that someone else can claim. CNAMEs to deleted cloud resources are the classic case, but released IPs and deleted delegated zones create the same risk.
Prevention is mostly about process. Remove DNS records before you delete resources, manage records alongside the infrastructure they point to, use platform domain verification, and keep an inventory of external targets. Back that up with regular automated audits, and you'll catch the occasional dangling record long before anyone else does.


