A subdomain takeover happens when your DNS still points one of your names at a cloud or SaaS resource you deleted, and someone else claims that resource. Their content then loads under your domain. To find one, list every name you publish, follow each CNAME to its target and ask whether that target still belongs to you. To fix it, remove the DNS record first and delete the resource last.
We ran the full chain in an isolated lab on 31 Oct 2026 and recorded the DNS answer at each step. Then we ran the same outside-in checks against our own domain. This playbook uses both, plus the guidance that Microsoft, AWS, GitHub, OWASP and MITRE publish.
How Does a Forgotten DNS Record Hand Over a Subdomain?
A team creates an app on a cloud platform and points a friendly name such as promo.example.com at it with a CNAME record. Months later the app is deleted, but the CNAME stays. Microsoft calls these leftovers “dangling DNS” entries and says CNAME records are especially vulnerable.
The platform now has a free app name that your DNS still sends visitors to. Anyone who creates an app with that same name receives your traffic. MITRE ATT&CK lists this as subdomain hijacking under technique T1584.001, used to borrow the trust of a real domain.
- 1Your DNS Cloud platformpromo CNAME points to app promo-2026
- 2Cloud platform Your DNSApp promo-2026 is deleted, the CNAME stays
- 3Attacker Cloud platformCreates a new app named promo-2026
- 4Your DNS AttackerVisitors to promo still follow the old CNAME
HTTPS does not save you here. Microsoft notes that whoever holds the hijacked name can apply for and receive a valid certificate. That certificate also gives them access to secure cookies.
CNAME is not the only record at risk. The OWASP prevention cheat sheet also lists three others:
- A records that point to a released IP address.
- NS records that delegate a subdomain to an outside DNS provider.
- MX records that point to a retired mail service.
What the DNS Showed at Each Step of Our Lab Run
Our lab used two zones on a CoreDNS 1.11.3 server in Docker: brand.example for the company and apps.provider.example for a pretend cloud platform. A small web front end played the platform. It served whichever tenant had claimed a host name. The .example names are reserved for testing, so nothing left the lab network.
The output below is trimmed to the status line and the answer records.
Step 1, healthy. The company’s CNAME points at its app, the app name resolves and the page is the company’s own.
$ dig @172.30.53.53 promo.brand.example CNAME +norec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54586
promo.brand.example. 300 IN CNAME promo-2026.apps.provider.example.
$ dig @172.30.53.53 promo-2026.apps.provider.example A +norec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8168
promo-2026.apps.provider.example. 60 IN A 172.30.53.80
$ curl -s -w " [HTTP %{http_code}]" --resolve promo.brand.example:80:172.30.53.80 http://promo.brand.example/
Brand Example spring promotion (owned by the marketing team) [HTTP 200]
Step 2, dangling. The app was deleted but the CNAME was not. The company record looks the same, while the target now answers NXDOMAIN, which means the name does not exist. This gap is the window an attacker looks for.
$ dig @172.30.53.53 promo.brand.example CNAME +norec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30275
promo.brand.example. 300 IN CNAME promo-2026.apps.provider.example.
$ dig @172.30.53.53 promo-2026.apps.provider.example A +norec
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 63559
$ curl -s -w " [HTTP %{http_code}]" --resolve promo.brand.example:80:172.30.53.80 http://promo.brand.example/
404 app not found: no tenant has claimed this host name on the platform [HTTP 404]
Step 3, taken over. A second tenant created an app with the old name. Both DNS answers now match step 1, apart from the query id. Only the page is different.
$ dig @172.30.53.53 promo.brand.example CNAME +norec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43208
promo.brand.example. 300 IN CNAME promo-2026.apps.provider.example.
$ dig @172.30.53.53 promo-2026.apps.provider.example A +norec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32907
promo-2026.apps.provider.example. 60 IN A 172.30.53.80
$ curl -s -w " [HTTP %{http_code}]" --resolve promo.brand.example:80:172.30.53.80 http://promo.brand.example/
LAB PROOF PAGE: this host name is now served by a different tenant [HTTP 200]
That last step is the lesson most guides skip. Once someone has claimed the resource, a DNS check that only asks “does it resolve?” passes. The AWS Security Blog makes the same point: resolution succeeds, but to the attacker’s resource. You have to compare the target against your own inventory or the platform’s error page.
How Do You Find Takeover Risks From the Outside?
Run these checks the way an outsider would, with public data and no access to your DNS console. This is the attacker’s-eye view of your domain. The OWASP Web Security Testing Guide test WSTG-CONF-10 uses the same approach.
- List every namePull your zone files and search Certificate Transparency logs, which record publicly trusted certificates.
- Find the CNAMEsRun dig NAME CNAME for each name and note every target on a cloud or SaaS platform.
- Resolve each targetTreat NXDOMAIN, SERVFAIL, REFUSED or no reachable server as a lead, as WSTG-CONF-10 advises.
- Read the pageFetch it and match platform errors such as NoSuchBucket or “There isn’t a GitHub Pages site here.”
- Confirm the ownerCheck that the account behind each target is yours, because a claimed target looks healthy.
- Check NS and MXRun dig NS and dig MX; a delegation you no longer run has the highest impact, per WSTG-CONF-10, and a retired mail host can receive your mail.
Steps 1 and 2 are the base of external asset discovery, and they often turn up names nobody remembers. Our free domain security checks post shows how to search Certificate Transparency logs for your domain. The OWASP cheat sheet lists more fingerprints, including Heroku’s “No such app” and Azure App Service’s “404 Web Site not found”.
Step 5 matters most on real platforms. In the WSTG example, a name points at a GitHub address that still resolves, and only the 404 page reveals the problem. Step 6 needs care too. The AWS Route 53 guide describes five ways a delegation can dangle, and Route 53 itself protects against only one of them.
What We Saw When We Checked Our Own Domain
We ran the same checks on brandefense.io on 31 Oct 2026, using the Cert Spotter API for Certificate Transparency data and a public resolver. The log search returned 35 unexpired certificates covering 11 names, four of them wildcards. We checked the seven named hosts, plus www and atlas.brandefense.io, our Atlas tool site.
- Three names are CNAMEs to outside SaaS platforms. Each one returned a Brandefense page, which shows the content there is still ours.
- No CNAME target answered NXDOMAIN, SERVFAIL or REFUSED.
- The other six hosts resolve straight to the same proxy addresses as our main site.
Two limits showed up that you will meet too. First, a made-up name under our domain returned status NOERROR but no answer records, so it had no address. A check that reads only the status line would count that name as live, so count the answer records too.
Second, the log search showed only current certificates. A name that never had its own certificate would not appear there at all.
How to Fix a Dangling Record, and in What Order
If the target is still unclaimed, delete the DNS record now. If someone else already serves content there, deleting the record still cuts off your visitors. Then report the account to the platform’s abuse team. Our misconfiguration response playbook covers what to check when a finding may already have been used.
For every planned shutdown, keep one order. The AWS Security Blog puts it simply: delete DNS first, wait for the TTL to expire, then delete the resource. TTL is how long resolvers may cache the old answer, often 300 to 3,600 seconds per OWASP.
Reversing the order opens the window from step 2 of our lab. When we removed the CNAME in a fourth lab run, the company name answered NXDOMAIN and no longer sent anyone anywhere.
Platform controls close part of the gap:
- Azure App Service: an asuid TXT record stops other Azure subscriptions from validating your custom domain. Microsoft warns it does not stop someone from creating an app with the name in your CNAME.
- Azure DNS alias records tie a record to the life of the resource behind it, but only for Front Door, Traffic Manager, CDN and public IP targets.
- GitHub Pages: verify your domain with a _github-pages-challenge TXT record and keep it in place. GitHub also warns that wildcard records put you at immediate risk of takeover, even when the domain is verified.
- Microsoft Defender for App Service alerts when a site is retired while its DNS entry stays. It does not scan for dangling entries that already exist.
Who Owns Each Step?
Deleting a cloud resource does not touch the DNS record that points at it. That is why the lasting fix is a process rule. Microsoft’s advice is to put “Remove DNS entry” on the required checks for every decommissioning. It also says to keep a catalog of endpoints and their application owners.
- Service owner: asks for DNS removal before deleting the resource, then confirms the name no longer resolves.
- DNS team: removes the record, waits for the TTL and keeps platform verification TXT records.
- Security team: runs the outside-in check on a schedule and follows up every lead with the named owner.
Names created outside IT, such as campaign sites and trial tenants, are where this breaks down, as our shadow IT and hidden attack surface article explains. Brandefense External Attack Surface Management scans an organisation’s internet-facing assets within minutes. Its output gives the security team a list of names to put through the checks above.
