Blog/Network Security
Network SecurityJuly 28, 2026 · 6 min read

Subdomain Takeover: Claiming the Domains You Forgot to Delete

You spin up a marketing microsite on a cloud host, point promo.yourcompany.com at it with a CNAME, run the campaign, and delete the host when it's over. The CNAME stays in DNS — nobody thinks to remove it. Months later, an attacker notices that promo.yourcompany.com still resolves to a cloud service name that no longer belongs to anyone. They register that name on the same platform, and now they control what promo.yourcompany.com serves. That's a subdomain takeover: a dangling DNS record pointing at a resource you gave up, which someone else can claim.

Why a forgotten subdomain is worth taking

The subdomain is worthless as infrastructure but valuable as trust. Still under your domain, an attacker can:

  • Host convincing phishing — a login page on a real *.yourcompany.com clears filters and fools users.
  • Steal cookies and tokens — cookies scoped to the parent domain, and OAuth/SSO flows that trust your subdomains, can leak to the attacker-controlled host.
  • Bypass allowlists — CSP, CORS, and SSO configurations that trust "any of our subdomains" now trust the attacker.
  • Damage reputation and SEO — malware or spam served from your brand's domain.

The takeover costs the attacker a free account on the same cloud platform. That asymmetry is why it's a favorite of bug-bounty hunters and real attackers alike.

How to find dangling records

The signature of a takeover-able record is a DNS entry whose target responds with a platform's "unclaimed" fingerprint — an S3 NoSuchBucket, a Heroku "No such app," a GitHub Pages 404, a Netlify "Not Found." Auditing is mechanical:

const dns = require('dns').promises;

// Fingerprints that indicate an unclaimed target you can lose.
const DANGLING = [
{ host: 's3.amazonaws.com',   body: 'NoSuchBucket' },
{ host: 'herokudns.com',      body: 'No such app' },
{ host: 'github.io',          body: "There isn't a GitHub Pages site here" },
{ host: 'netlify.app',        body: 'Not Found' },
];

async function check(subdomain) {
let cname;
try {
  cname = (await dns.resolveCname(subdomain))[0];   // the target name
} catch {
  return { subdomain, status: 'no-cname' };
}

const match = DANGLING.find((d) => cname.endsWith(d.host));
if (!match) return { subdomain, cname, status: 'points-elsewhere' };

// Fetch and look for the platform's 'unclaimed' body.
const res = await fetch('https://' + subdomain).catch(() => null);
const body = res ? await res.text() : '';
const dangling = !res || body.includes(match.body);
return { subdomain, cname, status: dangling ? 'TAKEOVER-RISK' : 'claimed' };
}

// Run 'check' over EVERY record in your DNS zones, on a schedule.

The principles

  • Delete DNS and the resource together. The takeover window opens the moment a record outlives its target. Make record deletion part of every decommission — ideally enforced by infrastructure-as-code.
  • Keep a DNS inventory. You can't protect records you've forgotten. Maintain a source of truth and flag anything pointing at a service no one owns.
  • Scan on a schedule. Automated fingerprint scanning catches dangling records before an attacker does; treat findings as high priority.
  • Constrain trust in subdomains. Don't blanket-trust *.yourdomain.com in CSP, CORS, cookies, or SSO — a single takeover shouldn't inherit your whole security boundary.

The one line: a DNS record that outlives what it points to is a free subdomain for an attacker — delete the record when you delete the resource.

Try it in the subdomain takeover simulation: find a dangling CNAME, claim the unowned target, and serve content from the victim's domain — then apply DNS hygiene and watch the opening close. It pairs with the DNS security guide on keeping the naming layer trustworthy.

Share this post

Frequently Asked Questions

Related posts