Can someone send mail as your domain? SPF, DKIM and DMARC from the outside

DedSec Security Research ·

A phishing message reaches the finance team. The From line reads [email protected], which is your domain, and the mail gateway shows that it passed SPF. The first question in the incident channel is how a message can pass authentication and still be forged.

It passed SPF for a different domain. The domain SPF checked and the domain the reader saw are separate fields, and the only thing connecting them is a DMARC record, which you may or may not have, set to a policy that may or may not do anything.

All three records sit in public DNS, so an attacker choosing which of your domains to impersonate sees what you see, in seconds. This post covers what each record authenticates, the misconfigurations visible from outside, and what an outside-in assessment can and cannot tell you.

Two senders in every message

An email carries two sender addresses, and much of the confusion about email authentication comes from treating them as one.

The envelope sender is the address in the SMTP MAIL FROM command. Bounces go there, and recipients rarely see it. The sending server also names itself in its HELO greeting. The header From, defined by RFC 5322, is the From line inside the message: the address the mail client displays and the person reads.

Nothing in SMTP requires the two to match. A bulk mail provider legitimately sends with an envelope sender at its own bounce domain and a header From at yours. An attacker can do the same.

SPF looks at the envelope. DKIM looks at whichever domain signed. Only DMARC asks whether either of those matches the From the reader sees.

SPF: which servers may use this envelope domain

SPF, specified in RFC 7208, lets a domain publish the hosts allowed to send mail with that domain as the envelope sender. The receiver takes the domain from MAIL FROM (RFC 7208 also recommends checking the HELO name separately), looks up a TXT record beginning v=spf1 at exactly that name, and evaluates it against the connecting IP address.

v=spf1 ip4:192.0.2.0/24 include:_spf.mail-provider.example mx -all

That reads: allow this range, allow whatever the provider's record allows, allow this domain's MX hosts, fail everything else.

What SPF does not do

An attacker who owns attacker.example publishes a valid SPF record for it, sends with MAIL FROM [email protected] and header From [email protected], and gets an SPF pass for attacker.example. Your SPF record is never consulted. However strict it is, SPF on its own does nothing about spoofing of the visible From. That gap is what DMARC closes.

The ten-lookup limit

RFC 7208 caps the DNS work one evaluation may cause. The terms that trigger DNS queries are the include, a, mx, ptr and exists mechanisms and the redirect modifier. Together they must not exceed 10, counted across every nested include; exceeding the limit returns permerror. The ip4, ip6 and all mechanisms need no lookup and do not count.

This is a common way for an SPF record to break unnoticed. Every SaaS sender asks for its include, each include may contain more, and the eleventh lookup usually arrives with a new vendor on a day when mail otherwise works. Under DMARC a permerror is not an SPF pass, so any mail relying on SPF alignment starts failing.

The same section sets two more rules. Each mx mechanism may lead to no more than 10 address lookups, or it returns permerror. And verifiers should limit void lookups, queries returning NXDOMAIN or an empty answer, to two, with permerror beyond that. Void lookups are often left over from a vendor you stopped using.

An include whose target publishes no SPF record at all also returns permerror under RFC 7208; it is not skipped. So an include of a vendor domain that has lapsed is either a permerror or, once someone re-registers the domain and publishes their own record, an authorisation for their servers, the case covered in how subdomain takeover works.

The ptr mechanism can go on sight. RFC 7208 says it should not be used: it is slow, depends on reverse DNS, and still counts toward the limit.

Exactly one record

If a name has more than one TXT record beginning v=spf1, RFC 7208 says evaluation returns permerror. This happens when a second team adds its own record instead of editing the existing one, and then neither works. One TXT query shows it.

Length is a related trap. A DNS TXT string holds at most 255 octets, so a long record is split into several strings, and RFC 7208 says receivers concatenate them without adding spaces. A split where the publisher assumed a space produces terms that run together and fail to parse. RFC 7208 also requires SPF to be published as TXT only; the old SPF record type is discontinued.

The all qualifier

The final all mechanism states what happens to a sender that matched nothing earlier.

Under DMARC the gap between -all and ~all matters less than people expect, because DMARC counts only an SPF pass and both are non-pass. One difference remains: RFC 7489 notes that some receivers reject on an SPF fail at SMTP time, before DMARC or DKIM is evaluated, which can lose forwarded mail that aligned DKIM would have passed.

Flattening, and why it rots

The usual fix for too many lookups is flattening: resolve each include to its current IP ranges and publish those as ip4 and ip6 terms, which cost nothing. It works on the day it is done. But the provider's ranges belong to the provider, and its include was how you learned when they changed. When the provider adds a range, mail from it fails SPF and nothing in your DNS shows why. Flattening holds only if something regenerates the record automatically and someone notices when that stops. The fix that lasts is fewer includes: drop unused vendors and move senders onto their own subdomains, each with a short record.

Forwarding and inheritance

When a message is forwarded unchanged, the forwarder connects to the final receiver from its own address while the envelope sender can still be your domain. That address is not in your record, so SPF fails at the second hop, and nothing in your record can fix it. This is why aligned DKIM, which survives unmodified forwarding, matters more than SPF for reaching p=reject.

SPF also has no fallback to a parent name. A record at example.com says nothing about mail.example.com, or about random-label.example.com, which does not exist. An envelope sender at a name with no record gets a result of none. Subdomain protection comes from DMARC.

DKIM: a signature tied to a domain

DKIM, specified in RFC 6376, has the sender sign selected headers and the body with a private key and add a DKIM-Signature header carrying d=, the signing domain, and s=, the selector. The receiver fetches the public key from a TXT record such as s1._domainkey.example.com and verifies the signature.

v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOC...

The signing domain need not be the header From domain. A provider can sign every customer's mail with its own domain, and that signature verifies while saying nothing about you. As with SPF, DMARC alignment is what ties it to the address the reader sees.

What a key record shows

If you know a selector, the key record tells you several things.

Selectors cannot be listed

This is the limit that matters most for outside-in work, so it is worth stating plainly: an external assessment cannot prove that a domain has no DKIM.

DNS has no operation that lists the names under _domainkey.example.com, and a reasonably configured server refuses zone transfers. An external check therefore queries guessed selectors, such as platform defaults like default, selector1, s1 or k1 and date-based names, and reports which exist. A domain signing with a selector nobody guessed looks exactly like a domain that does not sign.

There are partial exceptions. A DNSSEC zone using plain NSEC can be walked, revealing its names, and NSEC3 hashes can sometimes be reversed offline with a dictionary of likely labels. A query for _domainkey.example.com itself can hint: under RFC 8020 an NXDOMAIN answer means nothing exists beneath the name, while a name with descendants returns an empty answer without an error. Not every server handles empty intermediate names correctly, and under a wildcard an empty answer proves nothing, so that is evidence, not proof. The reliable way to learn a selector is to read the DKIM-Signature header of a message from the domain, which needs a message, not just DNS.

So an external DKIM check can honestly report either "these selectors were found, with these key properties" or "none of the selectors tried were found". The second does not mean DKIM is absent, and a report that says so claims more than DNS can show.

DMARC: tying authentication to the visible From

DMARC, specified in RFC 7489, is a TXT record at _dmarc.example.com beginning v=DMARC1. It defines what counts as authenticated for the header From domain, and states what the owner wants receivers to do with mail that is not.

v=DMARC1; p=reject; rua=mailto:[email protected]; adkim=r; aspf=r

Alignment

A message passes DMARC if it has an SPF pass whose envelope domain aligns with the header From domain, or a DKIM pass whose d= domain aligns with it. Either is enough.

Alignment is set separately for SPF with aspf and for DKIM with adkim. Relaxed, the default, requires the domains to share an organisational domain, so d=mail.example.com aligns with a From at example.com. Strict requires an exact match.

The attacker in the earlier example has an SPF pass for attacker.example, which does not align with example.com, and no DKIM signature from example.com, so DMARC fails. What happens next depends on the policy.

The policy, and what p=none means

p=reject asks receivers to reject failing mail. p=quarantine asks them to treat it as suspicious, typically delivering to spam. p=none asks them to change nothing and send reports.

p=none is monitoring, not protection. A domain at p=none is as spoofable as one with no DMARC record; the difference is that its owner can receive reports about it. It is a necessary stage, and also where some domains stop for good.

The pct= tag applies the policy to a percentage of failing mail, 100 by default. Under RFC 7489, mail not selected gets the next less strict policy: with p=reject and pct=25, the remaining failures are quarantined. A record left at a low pct for years is a policy that mostly does not apply.

Some errors degrade silently. If p= is missing or invalid but a valid rua is present, RFC 7489 says receivers should act as though p=none had been published. If a name has more than one DMARC record, policy discovery stops and DMARC is not applied at all. Both look like a working record to someone skimming DNS output.

Subdomains and the organisational domain

For a header From at shop.example.com, the receiver looks for _dmarc.shop.example.com. If there is none, it looks up the organisational domain, example.com, which RFC 7489 determines using a public suffix list, and applies its sp= policy, or p= if sp= is absent. There is no walk through intermediate labels: for a.b.example.com, the receiver checks a.b.example.com and then example.com, never b.example.com.

This describes RFC 7489. The IETF revision of DMARC, known as DMARCbis, replaces the public suffix list with a walk up the DNS tree and drops pct, so check which behaviour the receivers you care about implement.

So a strong policy at example.com covers names with no record of their own, including names an attacker invented. But sp=none at the organisational domain leaves every such subdomain unprotected while p=reject protects the parent. It is an easy setting to choose when trying not to break an unknown subdomain sender, and it undoes most of the policy.

Reports, and where they go

The rua= tag lists addresses for aggregate reports: periodic summaries, by sending source, of mail using your domain that passed or failed SPF, DKIM and alignment. The ruf= tag requests per-message failure reports, which receivers are not required to send.

Aggregate reports are the only way to see what actually sends as your domain, and one rule routinely stops them arriving. When a rua address is at another domain, such as mailto:[email protected] for a policy at example.com, RFC 7489 requires receivers to confirm the destination agrees to receive them by looking for a TXT record beginning v=DMARC1 at example.com._report._dmarc.example.net. If it is missing, reports to that address are not sent.

That is checkable from outside. A rua at a domain without the authorisation is a deployment collecting nothing, and its owner may read the silence as nothing to report. It often follows a change of reporting vendor.

The domain an attacker picks

An attacker does not need your main domain. Any domain recognisably yours will do: the name you rebranded from, a country domain, a launch domain, a domain bought defensively and never used. These are the least likely to have been touched by whoever configured mail authentication, because they send no mail and nobody thought they needed records.

They are also the easiest to protect, because a domain that sends nothing can publish the strictest policy with no risk of breaking anything:

The first step is an inventory, and it is an attack-surface question rather than a mail question: which domains do you hold, including those registered by an agency or acquired with a company? You cannot lock down a domain you do not know you own.

Lookalike domains are a separate problem. DMARC protects only domains you control. An attacker who registers your name with one character swapped, a support- prefix, or under another top-level domain publishes their own SPF, DKIM and DMARC, and their mail passes all three, correctly, as their domain. Nothing in your zone affects it. The defences are detecting the registration, takedown and user awareness, covered in breach and brand monitoring attack paths.

What an outside-in assessment cannot see

From DNS alone, an assessment can show what each domain declares: whether SPF parses and what it costs in lookups, how it ends, duplicate records, includes of domains that no longer exist, which guessed DKIM selectors exist and with what keys, which DMARC policy applies to each name, and whether report destinations are authorised. That covers failures in the declaration itself.

It cannot show whether the declaration matches reality.

An external finding is a statement about your declaration. Deciding whether it is correct needs someone who knows which systems send your mail.

From p=none to p=reject without breaking mail

Domains stay at p=none for fear of rejecting legitimate mail, which is reasonable while the sender inventory is unknown. This order removes the fear:

  1. Publish p=none with a working rua. If reports go to another domain, confirm the authorisation record exists, then wait until reports actually arrive. A silent rua is not a clean result.
  2. Inventory senders from the reports. Over several weeks, group sources by IP address and authenticated domain, and sort them into your systems, third parties sending for you, forwarders and lists, and abuse.
  3. Align each legitimate sender. Prefer DKIM with d= set to your domain or a subdomain, on a key of at least 2048 bits, because it survives forwarding. Where a platform allows a custom envelope domain under yours, set it so SPF aligns too.
  4. Clean up SPF. Remove unused includes, get to ten lookups or fewer without flattening if you can, remove ptr.
  5. Move to p=quarantine at a low pct, then raise it, checking reports at each step for legitimate sources failing.
  6. Move to p=reject, and set sp= deliberately rather than inheriting it by accident.
  7. Lock down non-sending domains now. They have no legitimate senders to break, so they do not need steps one to six.
  8. Keep reading reports. A vendor onboarded without DKIM alignment will now fail, and the reports show it first.

Mailing lists that rewrite the subject or append a footer break DKIM, and because the list resends under its own envelope domain, SPF no longer aligns either. ARC, specified in RFC 8617, carries authentication results across such intermediaries, but whether a receiver trusts them is its own choice.

What to ask of any external email-authentication check

SPF, DKIM and DMARC are three public statements your domain makes. An attacker reads them before choosing which domain to send from, so read them first.

Where Maphra fits

Maphra starts from a domain name, enumerates subdomains and attributes each discovered asset back to the organisation that owns it. Its exposure analysis includes DNS and email authentication checks across every discovered asset, and each finding carries the evidence that produced it. Discovery re-runs on a schedule and is diffed against the last known surface, so what appeared, changed or disappeared between runs is reported. Its brand monitoring includes look-alike domain detection, the problem DMARC does not reach.

The limits above apply to any external check, ours included: it sees what your domains declare, not whether your legitimate mail passes. Reading aggregate reports, inventorying senders and rolling out policy are work for your own team, and the questions above apply to any tool, this one included.

More about Maphra · All posts