The Sender Nobody Owns: Walking an SPF Include Tree to NXDOMAIN
The challenge
Three of Meridian's customers paid an invoice that Meridian never sent, and the fake mail passed SPF. Nobody touched the mail servers, so the answer is in the DNS. Meridian's SPF record delegates to other senders with include:, and each of those records can delegate again. Walk the tree until one lookup comes back NXDOMAIN. That name is unregistered, which means anybody can register it, publish an SPF record on it, and send mail that authenticates as Meridian. Name it.
What you'll learn
- Resolve an SPF record and follow every include: to its own record
- Recognise NXDOMAIN on an include as a takeover-ready delegation
- Explain why a vendor's SPF record is part of your own attack surface
- Use dig to walk a delegation tree one name at a time
- Connect vendor offboarding to authentication failures months later
Skills tested
Prerequisites
- What a DNS TXT record is
- Basic familiarity with dig
How it works
An SPF record answers one question: which hosts may send mail for this domain. The answer is rarely written in one place. include: tells the evaluator to go and read somebody else's SPF record and treat its result as part of yours, and that record is free to contain includes of its own. What looks like a single line in your zone is a tree that can be four or five lookups deep, most of it published by companies you do not control.
A branch of that tree stops being safe the moment a name in it stops resolving. NXDOMAIN is not a failure that closes the door: it means nobody owns the name. Anyone can register it, publish v=spf1 +all, and every SPF evaluator in the world will walk from your domain into their record and return a pass. The victim domain's own DNS is untouched and perfect, which is why the team that owns it usually looks at everything except the tree.
Vendor offboarding is where these appear. The contract ends, the account closes, the domain lapses at renewal, and the include that pointed at it stays in a record that a third party maintains. Nothing breaks visibly, so nothing gets cleaned up.
Common mistakes
- Stopping at the first record. The three includes in
meridian.exampleare the question, not the answer. Two are leaves; one delegates again. - Answering
spf.mailhive.example. MailHive resolves fine and is a live vendor. It is the record that carries the broken include, not the broken name. - Reading only the ANSWER SECTION. A dead lookup has no answer section at all. The finding is on the
status:line of the header. - Assuming the mail servers were compromised. Nothing was. SPF passed honestly, for a sender the record genuinely authorises.
How to defend against it
Treat the include tree as inventory, not configuration.
- Resolve every include recursively on a schedule and alert on any name that returns NXDOMAIN or SERVFAIL. This is a few lines of script and it catches the problem the day the domain lapses.
- Put DNS delegation removal on the vendor offboarding checklist next to revoking API keys, and include the vendor's own records where they name you.
- Publish DMARC with a policy and collect the aggregate reports. A spoofing run that passes SPF from an unfamiliar network still shows up there with a source you do not recognise.
- Keep the lookup count low. SPF allows ten DNS lookups; a shallow record is both faster and much easier to audit.