SPF, DKIM, and DMARC: Why “Valid” Records Still Let Your Domain Be Spoofed

SPF, DKIM, and DMARC: Why “Valid” Records Still Let Your Domain Be Spoofed

SPF、DKIM 和 DMARC:为什么“有效”的记录仍然无法阻止你的域名被仿冒

Originally published on the Merlonix blog. There are two different questions about your domain’s email authentication, and almost every checker answers only the first. 本文最初发布于 Merlonix 博客。关于域名的电子邮件身份验证,存在两个不同的问题,而几乎所有的检测工具都只回答了第一个。

The first is do you have SPF, DKIM, and DMARC records — a presence question, a yes/no lookup. The second is do those records actually stop someone from sending email that looks like it came from you — an enforcement question. 第一个问题是:你是否拥有 SPF、DKIM 和 DMARC 记录?这是一个关于“存在性”的问题,即简单的“有/无”查询。第二个问题是:这些记录是否真的能阻止他人发送看起来像是来自你的电子邮件?这是一个关于“执行力”的问题。

You can pass the first and fail the second completely, and the gap between them is the whole game: a domain with all three records published, every free checker showing green, that a spammer can still spoof at will because each record is published in its permissive, do-nothing mode. 你可能通过了第一个检查,却在第二个检查上彻底失败,而这两者之间的差距正是问题的关键所在:一个域名可能发布了所有三项记录,所有免费检测工具都显示绿色通过,但垃圾邮件发送者仍然可以随意仿冒,因为每一项记录都处于宽松的、不作为的模式下。

The permissive modes exist for a good reason — they’re how you roll these records out without bouncing your own legitimate mail. The problem is that “published it in monitor mode so I could watch first” and “finished” look identical to a tool that only checks presence, and an enormous number of domains stop at the first and never come back. 这些宽松模式的存在是有充分理由的——它们让你能够在不拦截自己合法邮件的情况下部署这些记录。问题在于,对于只检查“存在性”的工具来说,“为了先观察而以监控模式发布”和“已完成部署”看起来是一模一样的,而大量的域名在完成第一步后就停滞不前,再也没有后续动作。

Here’s what actually decides enforcement, record by record, and how to tell which mode yours is in. 以下是决定执行力的具体因素(按记录逐一分析),以及如何判断你的记录处于哪种模式。

SPF: only -all actually rejects

SPF:只有 -all 才能真正实现拒绝

An SPF record lists which servers are allowed to send mail as your domain, and it ends in an all mechanism that says what a receiver should do with a server that isn’t on the list. That final qualifier is the entire enforcement decision, and there are four of them: SPF 记录列出了哪些服务器被允许以你的域名发送邮件,它以一个 all 机制结尾,该机制规定了接收方应如何处理不在列表中的服务器。这个最后的限定符决定了整个执行策略,共有四种:

  • -all (hardfail) — “reject mail from any server not listed.” This is the only one that protects you. -all (硬失败) — “拒绝来自任何未列出服务器的邮件。”这是唯一能保护你的选项。
  • ~all (softfail) — “accept it but mark it suspicious.” Receivers still deliver it. Softfail is the rollout setting, and it’s where most records get stranded. ~all (软失败) — “接收邮件但将其标记为可疑。”接收方仍然会投递这些邮件。软失败是部署阶段的设置,也是大多数记录停滞不前的地方。
  • ?all (neutral) — “no opinion.” Functionally the same as having no policy on the all term. ?all (中立) — “无意见。”在功能上等同于对 all 项没有设置策略。
  • +all — “any server on the internet may send as this domain.” This is actively worse than no SPF at all, and it’s usually a copy-paste accident. +all — “互联网上的任何服务器都可以以该域名发送邮件。”这比完全没有 SPF 还要糟糕,通常是复制粘贴导致的失误。

So an SPF record can be present, syntactically perfect, and end in ~all — and it stops nothing. A checker that reports “SPF: found” tells you nothing about whether the record does its job. The one that matters reads the qualifier on the all mechanism. 因此,一个 SPF 记录可能存在、语法完美且以 ~all 结尾——但它什么也阻止不了。报告“SPF: 已找到”的检测工具无法告诉你该记录是否发挥了作用。真正重要的是读取 all 机制上的限定符。

There’s a second SPF trap that hides even from people who know about -all: the RFC 7208 ten-lookup limit. SPF evaluation is capped at 10 DNS lookups, and every include: (each mail vendor you add — your ESP, your CRM, your helpdesk, your invoicing tool) can cost one or more. SPF 还有第二个陷阱,即使是了解 -all 的人也容易忽略:RFC 7208 规定的 10 次查询限制。SPF 评估上限为 10 次 DNS 查询,而每一个 include:(你添加的每个邮件供应商——如 ESP、CRM、客服系统、发票工具)都可能消耗一次或多次查询。

Go over 10 and receivers return a PermError and stop evaluating SPF entirely — a record that read as fine yesterday silently stops working the day you added one more vendor. Nothing in your DNS changed to warn you; the count just crossed a line. This is why “we set up SPF once” is not a durable state: SPF strength is a function of how many senders you’ve bolted on since. 超过 10 次,接收方会返回 PermError 并完全停止评估 SPF——昨天看起来正常的记录,在你添加了另一个供应商的那天起就悄无声息地失效了。你的 DNS 中没有任何变化来提醒你;只是查询次数越过了界限。这就是为什么“我们设置过一次 SPF”并不是一个持久的状态:SPF 的强度取决于你此后又添加了多少发送方。

DMARC: p=none is a smoke detector, not a lock

DMARC:p=none 是烟雾报警器,而不是锁

DMARC is the record that ties SPF and DKIM together and tells receivers what to do when a message fails both. Its p= tag is the enforcement switch, and it has three settings: DMARC 是将 SPF 和 DKIM 结合在一起的记录,它告诉接收方当邮件两者都验证失败时该怎么做。它的 p= 标签是执行开关,有三种设置:

  • p=none — “check, report, but deliver anyway.” This is monitor-only. Forged mail still lands in the inbox. It exists so you can collect DMARC aggregate reports and see who’s sending as you before you turn on teeth. p=none — “检查、报告,但照常投递。”这仅用于监控。伪造的邮件仍然会进入收件箱。它的存在是为了让你在启用强制策略前,收集 DMARC 汇总报告并查看谁在以你的名义发送邮件。
  • p=quarantine — “send failing mail to spam.” p=quarantine — “将验证失败的邮件发送到垃圾邮件文件夹。”
  • p=reject — “refuse failing mail outright.” This is the goal. p=reject — “直接拒绝验证失败的邮件。”这是最终目标。

Here is the single most common email-auth state on the internet: a domain publishes a DMARC record, a checker shows “DMARC: present,” everyone moves on — and the policy is p=none. Published and unenforced. The record is doing exactly one useful thing (generating reports) and zero protective things (forged mail is still delivered). 互联网上最常见的电子邮件身份验证状态是:域名发布了 DMARC 记录,检测工具显示“DMARC: 已存在”,大家就此作罢——但策略却是 p=none。已发布但未执行。该记录只做了一件有用的事(生成报告),而保护作用为零(伪造的邮件仍然被投递)。

p=none is a smoke detector: it tells you about a fire, it doesn’t put one out. Only p=quarantine or p=reject actually stops a spoof. p=none 是烟雾报警器:它告诉你发生了火灾,但并不能扑灭它。只有 p=quarantinep=reject 才能真正阻止仿冒。

Two more DMARC tags quietly undercut a policy that looks enforced: sp= sets the policy for subdomains. A record with a strong p=reject but sp=none leaves every subdomain — mail., news., billing. — fully spoofable, and attackers know to try them. pct= applies the policy to only a percentage of mail. p=reject; pct=20 enforces on one message in five and delivers the other four. It’s a rollout dial people forget to turn back to 100. Presence tells you none of this. Enforcement is p=, sp=, and pct= read together. 另外两个 DMARC 标签会悄悄削弱看起来已执行的策略:sp= 设置子域名的策略。一个拥有强力 p=rejectsp=none 的记录,会让每一个子域名(如 mail.、news.、billing.)完全处于可被仿冒的状态,而攻击者深知这一点。pct= 仅将策略应用于一定比例的邮件。p=reject; pct=20 表示只对五分之一的邮件执行拒绝,其余四分之四则正常投递。这是一个人们常忘记调回 100 的部署进度条。仅凭“存在性”检查无法发现这些。执行力取决于 p=sp=pct= 的综合解读。

DKIM: the leg you can’t fully check from the outside

DKIM:从外部无法完全检查的一环

DKIM signs each message with a private key and publishes the matching public key in DNS, so a receiver can verify the message wasn’t altered and really came from an authorized sender. It’s the third leg of the triad — and it’s the one that behaves differently from a checker’s point of view, in a way worth understanding before you trust any “DKIM: not found” result. DKIM 使用私钥对每封邮件进行签名,并在 DNS 中发布匹配的公钥,以便接收方验证邮件未被篡改且确实来自授权发送者。这是三要素中的第三环——从检测工具的角度来看,它的表现方式很特别,在信任任何“DKIM: 未找到”的结果之前,有必要了解这一点。

The catch is the selector. DKIM keys don’t live at a fixed DNS name; they live at ._domainkey., and the selector is chosen by the sender. Google Workspace uses google, Microsoft 365 uses selector1/selector2, SendGrid, Mailchimp, and the rest each pick their own — and there is no way to enumerate a domain’s selectors from DNS. You cannot ask “what are this domain’s DKIM keys?” There’s no record that lists them. 关键在于选择器(selector)。DKIM 密钥并不位于固定的 DNS 名称下;它们位于 <selector>._domainkey.<yourdomain>,而选择器是由发送者选择的。Google Workspace 使用 google,Microsoft 365 使用 selector1/selector2,SendGrid、Mailchimp 等各自选择自己的——而且无法从 DNS 中枚举一个域名的所有选择器。你无法询问“这个域名的 DKIM 密钥是什么?”因为没有记录会列出它们。

So any external DKIM check does the only thing it can: it probes a curated list of the well-known selectors the major providers use and reports which ones answer. That makes a hit definitive — “DKIM is configured, here’s the selector” — but a miss is not: it means “none of the common selectors resolved,” which is a very different statement from “this domain has no DKIM.” A domain using a custom selector is signing every message correctly and will show up blank on a selector probe. Read a DKIM “not found” as “verify by hand,” never as “broken.” (Any tool that reports a flat “no DKIM” from outside is overstating what it can actually know.) 因此,任何外部 DKIM 检查只能做一件事:探测主要服务商使用的知名选择器列表,并报告哪些有响应。如果命中,结果是确定的——“DKIM 已配置,选择器是这个”——但如果未命中,则不代表什么:它只意味着“没有一个常用选择器解析成功”,这与“该域名没有 DKIM”是完全不同的概念。一个使用自定义选择器的域名可能正在正确地签署每一封邮件,但在选择器探测中会显示为空白。请将 DKIM “未找到”解读为“需手动验证”,绝不要解读为“已损坏”。(任何从外部直接报告“无 DKIM”的工具,都夸大了它实际能获取的信息。)

MTA-STS: enforce, not testing

MTA-STS:执行,而非测试

The same presence-versus-enforcement split shows up one layer down, in whether inbound mail to you is encrypted in transit. MTA-STS (RFC 同样的“存在性”与“执行力”的分裂也出现在更深一层,即你收到的邮件在传输过程中是否加密。MTA-STS (RFC…