"Subdomain Takeover: How Dangling DNS Records Become Someone Else's Website"

Subdomain takeover is the vulnerability class that starts with good housekeeping done badly: a team points status.example.com at a StatusPage instance, promo.example.com at an S3 bucket, docs.example.com at GitHub Pages — the campaign ends, the vendor contract lapses, the repo is renamed, the bucket is deleted. Everyone forgets the resource. Nobody deletes the DNS record. The CNAME stays, cheerfully resolving to a service that will happily hand that same resource to the next person who asks for it.

That next person can be an attacker. If they claim the now-orphaned resource — recreate the bucket name, re-register the Heroku app, republish the GitHub Pages site — every request to your subdomain is served from their infrastructure, under your hostname. Cookies for example.com scope to it. OAuth callbacks trust it. CSP allowlists and CORS origins include it. TLS certificates can be issued for it via public CAs. It is, functionally, your website — compromised without a single request touching your servers.

This guide walks the anatomy of the bug (DNS → claim → abuse), why it is almost always a process failure rather than a technical one, which services have exploitable claim behaviors, and how detection works — including what our scanner does when it probes your subdomains. The free security scan includes subdomain takeover detection with per-service fingerprints.

Table of contents

  1. Anatomy: DNS records outlive resources
  2. Why a claimed subdomain is game over
  3. Which services are vulnerable
  4. How detection actually works
  5. Prevention: making takeover impossible
  6. A response playbook for a confirmed takeover
  7. Frequently asked questions

Anatomy: DNS records outlive resources

The vulnerability needs two ingredients, and either alone is harmless:

  1. A DNS record (usually a CNAME) pointing at a third-party service's resource namespace. Examples:
    
    promo.example.com.   CNAME   example-promo.s3.amazonaws.com.
    status.example.com.  CNAME   example-company.statuspage.io.
    docs.example.com.    CNAME   exampleorg.github.io.
    
  2. That resource no longer exists. The bucket was deleted, the Heroku app destroyed, the Pages site unpublished, the Zendesk instance cancelled.

Because DNS delegation is hierarchical, example.com's operators delegated promo to whatever example-promo.s3.amazonaws.com resolves to. S3's answer for a nonexistent bucket is a machine-readable "NoSuchBucket" error — served, notably, from the subdomain's hostname. The dangling record doesn't just leak that the resource is gone; it lets anyone recreate the resource under the same name and serve content in your namespace.

The claim step depends on the service's namespace rules, and this is what separates vulnerable services from safe ones:

  • Claimable after release (the dangerous kind): S3 bucket names are globally unique and free once released — anyone can create example-promo. Heroku app names, *.firebaseapp.com sites, *.gitlab.io projects and Azure *.cloudapp.azure.com names work the same way: the name is available to the first taker after you let it go.
  • Not claimable by third parties: many SaaS vendors bind custom domains to your verified account (Cloudflare for SaaS with pre-validation, Fastly, modern Zendesk) — deleting your instance leaves the CNAME dangling but the service answers with their 404 or an ownership error that nobody else can claim. Dangling, yes; takeover, no.

So a dangling CNAME is a candidate, not automatically a vulnerability — the severity is exactly the difference between "service 404s" and "service hands out the name." Scanners that report every dangling record as Critical are crying wolf; the meaningful signal is the service-specific verdict.

Why a claimed subdomain is game over

It's tempting to shrug: "it's just promo.example.com, who cares?" The impact comes from everything that trusts the parent domain and now extends that trust to attacker content:

  • Cookies. Cookies set with Domain=example.com are sent to every subdomain, including the attacker's. Any user who visits the takeover page hands their apex-domain cookies to attacker code. (Host-only cookies, per our cookie guide, sharply reduce this exposure.)
  • OAuth and SSO callbacks. If any flow lists *.example.com or a specific subdomain as a valid redirect URI — common in hastily-configured SaaS SSO — the attacker can receive authorization codes and tokens meant for your app.
  • CORS allowlists. Any API that echoes or allowlists https://*.example.com origins now grants the attacker's origin read access to authenticated responses (see the CORS guide for what that means mechanically).
  • CSP and content allowlists. script-src https://*.example.com or admin panels whitelisting subdomains turn the takeover into persistent XSS on your main site.
  • TLS certificates. The attacker can obtain a publicly-trusted certificate for promo.example.com via HTTP-01 validation — because they serve the HTTP-01 challenge on the subdomain. Browsers see a valid padlock on attacker infrastructure with your branding one DNS hop away.
  • Phishing with perfect provenance. hr-benefits.example.com serving a plausible login page is categorically more convincing than hr-benefits-example.com. Employees check the domain; the domain checks out.
  • Email (SPF/heuristics). Subdomains sometimes appear in SPF records or are trusted by internal tooling; a takeover can pivot into spoofing internal-looking senders.

None of this requires your main infrastructure to be touched, which is why takeovers persist for months: every internal scan of "the website" is green while promo.example.com quietly serves attacker content.

Which services are vulnerable

The community-maintained can-i-take-over-xyz catalogue tracks claimability per service; the short version of the services that matter most, grouped by their dangling-record behavior:

Fingerprint-confirmed, historically claimable (the scanner probes these with exact body/status signatures):

Service Dangling CNAME target Unclaimed signature
AWS S3 *.s3.amazonaws.com 404 + NoSuchBucket
Heroku *.herokuapp.com 404 + "No such app"
GitHub Pages *.github.io 404 + "There isn't a GitHub Pages site here"
Azure Web Apps *.azurewebsites.net 404 + "404 Web Site not found"
CloudFront *.cloudfront.net 403/404 + "The request could not be satisfied"
Firebase Hosting *.firebaseapp.com, *.web.app 404 + "Site not found"
Zendesk *.zendesk.com 404 + "Help Center Closed"
Freshdesk *.freshdesk.com 404 + "We couldn't find this page"
Shopify *.myshopify.com 404 + "Sorry, this shop is currently unavailable"
Bitbucket / GitLab Pages *.bitbucket.io, *.gitlab.io 404 + repo/page-not-found bodies
ReadTheDocs, WP Engine, Pantheon, Tumblr, HelpJuice, Unbounce vendor-specific distinctive 404 bodies

Notes on the nuanced cases:

  • S3 today: since 2021, S3 applies name-transfer rules — a released bucket name in us-east-1 is blocked from immediate re-registration (for days to months depending on region), which has turned many historically-critical S3 findings into dangling-but-not-currently-claimable. The scanner still reports them (Medium, "needs review") because the block is temporary and the record is still a time bomb.
  • CloudFront: dangling distributions are often not claimable (AWS requires the CNAME be added to a distribution you own, and unverified custom CNAMEs get rejected) but their error pages fingerprint like the vulnerable case — hence "needs review" rather than confirmed-critical.
  • Azure: *.cloudapp.azure.com names are re-registerable, *.azurewebsites.net historically required the attacker to also control the DNS validation — the fingerprint library tracks which variant is exploitable right now.

The scanner's rule of thumb, and the right one for your own triage: a fingerprint match means "investigate today"; an NXDOMAIN CNAME target means "the record is dead — remove it"; a healthy mapping is informational.

How detection actually works

Detection decomposes into three questions per candidate subdomain:

1. Does it have a CNAME at all? Resolve the candidate names (www, api, staging, dev, promo, docs, status, mail, admin, …) under your domain. No CNAME → nothing to take over via this vector. (A records pointing at released cloud IPs are a different, rarer vector — IP reassignment — not covered by CNAME-based scanning.)

2. Does the CNAME target match a known service? The target's suffix identifies the service — .s3.amazonaws.com, .herokuapp.com, .github.io — and each service has a deterministic "this resource does not exist" response: a specific status code plus a specific body string. Matching is done on the CNAME target, not the response, first — because the service's response only tells you the resource is gone, while the target tells you which service's claim rules apply.

3. Is the resource claimable or just gone? The scanner fetches the URL the CNAME resolves to and compares the response against the service's unclaimed-resource fingerprint (status + body). Three verdicts result:

  • Fingerprint matches → confirmed dangling and unclaimed → Critical: "Dangling DNS record: subdomain takeover possible." Anyone can claim this today.
  • Known service suffix but response doesn't match the unclaimed fingerprint (service healthy, or the modern non-claimable 404) → Medium: "CNAME to takeover-capable service needs review." The record may be intentional; verify.
  • CNAME target itself NXDOMAIN → the record points at a name that no longer exists at all → dangling record, remove it.

Our probe list covers the standard subdomain names (dev/staging/test/beta/demo, api/admin/portal, plus infra names like jenkins, grafana, kibana, backup, db — the ones that most often get CNAME'd to cloud resources and forgotten), is extensible per-scan via the subdomain_names policy parameter, and supports a subdomain_ignore list so legacy-but-intentional records don't produce noise. Probe volume is bounded (40 subdomains per scan) so scans stay fast and polite to the services involved.

You can reproduce the core check manually for a single suspect:

# 1. CNAME present?
dig +short CNAME promo.example.com
# example-promo.s3.amazonaws.com.

# 2. What does the service say?
curl -s https://promo.example.com | head -5
# <?xml...><Error><Code>NoSuchBucket</Code>...  → confirmed unclaimed

If step 2 returns NoSuchBucket, "No such app", "There isn't a GitHub Pages site here" or a vendor equivalent, the subdomain is claimable — remove or re-point the record today.

Prevention: making takeover impossible

Takeover is fundamentally a lifecycle failure, so prevention is process:

  1. Inventory DNS records and their owners. Every CNAME should have an entry: what service, what resource, who owns it, when it was last verified. Zone files grow silently; without an inventory, dangling records are invisible by default. Cloudflare and Route53 both support tags/notes — use them.
  2. Destroy records in the same change as the resource. Make "delete the CNAME" a line item on the same ticket as "terminate the Heroku app / delete the bucket." The failure mode is never "we forgot the resource" — it's "we forgot the record."
  3. Point at your own hostname, not the vendor's. Where the service allows it, CNAME to an intermediate under your control (promo.example.com → gateway.example.com → vendor) so a vendor-side deletion leaves your record resolveable (you control the next hop) instead of dangling at the vendor's namespace.
  4. Prefer services with domain-claim validation. Vendors that require you to prove domain control before serving content (modern Cloudflare-for-SaaS, Fastly, GitHub Pages with verification) reduce takeover to a nuisance even with dangling records.
  5. Scan continuously, not once. Records drift constantly — campaigns create subdomains faster than cleanup runs. A periodic takeover scan (plus the per-scan subdomain_names extension for new campaign names) catches the dangling record within a cycle instead of within a year.
  6. Constrain the trust boundary anyway. Host-only cookies (no Domain=), exact-match CORS origins instead of wildcard-subdomain allowlists, and enumerated (not globbed) OAuth redirect URIs each shrink the blast radius of the next takeover — of yours or anyone's.

A response playbook for a confirmed takeover

If a scan or a researcher report tells you a subdomain is claimable:

  1. Remove or re-point the DNS record immediately — this is the only urgent step, and it takes minutes. The vulnerability is the record.
  2. Check whether someone already claimed it. Fetch the subdomain: vendor 404 (still unclaimed) vs. unknown content (claimed). If claimed, you have an active incident, not just a config fix.
  3. If claimed: assess exposure window. When did the record go dangling? Any cookies with Domain= scope, OAuth callbacks, or API origins referencing that subdomain were exposed since then. Invalidate sessions where prudent, rotate secrets that lived in those flows.
  4. Search for abuse evidence: certificate transparency logs for certs issued to the subdomain, webserver/CDN logs for the hostname, any brand mentions of the subdomain in the wild.
  5. Fix the process, not just the record — the same audit that found one dangling CNAME usually finds its siblings. Rescan until clean, then put the scan on a schedule.

Frequently asked questions

Is a dangling CNAME always a takeover vulnerability?

No — it's a candidate. The vulnerability exists only if the target service allows third parties to claim the released name. The service fingerprint (its unclaimed-resource response) plus claim-rules determine severity; that's why scanners emit both confirmed-critical and needs-review verdicts.

Can A records (not just CNAMEs) be taken over?

Yes, via a different mechanism: an A record pointing at a released IP address that another customer now controls (historically seen with certain cloud ranges). CNAME-based detection won't catch it; it requires IP-range analysis, which is why our scan focuses on the far more common CNAME vector and flags A records to private/odd IPs separately in the DNS module.

The service returns 404 but my vendor says it's secure — who's right?

Both can be: a 404 with the vendor's branded error (not the unclaimed-resource fingerprint) usually means the platform reserves the namespace. The fingerprint is the discriminator — "404 Web Site not found" from Azure means something different than "404 Web Site not found" plus a re-registerable name.

How do attackers find vulnerable subdomains at scale?

They run the same pipeline scanners do — enumerate subdomains (certificate transparency logs, brute-force lists, passive DNS), resolve CNAMEs, fingerprint the responses — against millions of domains continuously. Assume any confirmed-dangling record you own is discovered within days.

Does HTTPS make this irrelevant?

No — the attacker can obtain a valid certificate via HTTP-01 validation because they control the HTTP endpoint after takeover. The padlock proves nothing about who operates the subdomain.


Run the free security scan to check your subdomains against the current fingerprint library — alongside TLS, headers, cookies, CORS and the rest of the checks.