Chips

域名卫生:TXT、SPF、DMARC 与一个「看起来像真站」的域名

· 约 7 分钟 · 2436 字

一个没有任何 TXT 记录的域名,比没有网站更早暴露「临时域」的身份。SPF、DMARC、CAA 各在防什么、最小写法如何,以及一组可执行的 dig 检查序列和每项有值与留空的判读依据。

没有 TXT,本身就是一条记录

判断一个域名是否「正经」,常见做法是看它有没有网站。但更早、更基础的信号在 DNS 里:TXT 记录。

长期经营或正常使用的域名,几乎必然带若干 TXT:企业邮箱的 SPF、DMARC 策略、搜索与办公平台的域归属验证串、各类 SaaS 的 ownership 令牌。这些记录本身没有访问者可见的效果,是运维顺手加上去的副产品。反过来说,一个域名如果 A 记录指向一个平平无奇的主机、TXT 区却完全空白,在扫描器、邮件网关和反钓鱼系统的评分里,就已经落进了「新注册 / 未配置 / 可能是一次性」这一档。

这个判断按性质是概率性的,不是定理。只做静态托管、完全不发邮件的小站确实可能一条 TXT 都没有。所以正确的应对不是伪造一堆记录,而是把「本域不做这些事」显式写出来。DNS 恰好提供了这种表达方式,写一次,之后零维护。下面几类记录就是这种表达。

SPF:用一条记录声明「本域不发信」

SPF 是根域上一条普通 TXT,以 v=spf1 开头。对一个不发送任何邮件的域,最小写法是:

example.com.    IN TXT    "v=spf1 -all"

v=spf1 声明这是 SPF 记录。all 是匹配一切来源的机制,本身不带限定符时等价于 neutral。前面的限定符决定结论等级:

限定符 结论 接收方通常的处理
+all pass 任何人都被授权,等于没有策略
?all neutral 不做判断,按其他信号走
~all softfail 通常标记可疑但仍投递
-all fail 判定未授权,按 DMARC 策略处置

-all 前面没有任何机制,于是等价于「本域不从任何地方发信」。它防的是别人把你的域当作信封发件人(envelope sender)来伪造邮件:接收方查你的 SPF,得到 fail,配合 DMARC 就能直接处置。若这个域根本没有 SPF,接收方只能得到 none,伪造邮件少了这一层阻力——这正是钓鱼与垃圾邮件偏好使用未配置域名的原因之一。

两个容易忽略的点。其一,SPF 校验的是信封发件人,不是收件人看到的 From: 头,光有 SPF 并不能阻止「用你的域做 From: 显示名」的伪造,这部分要靠 DMARC 对齐。其二,一个域只能有一条 SPF 记录,出现两条 v=spf1 是配置错误,接收方会直接判定为 permerror。

查询方式:

$ dig +short TXT example.com
"v=spf1 -all"

把这行换成任意一个 TXT 为空的域,输出就是什么都没有——这就是开头那条论点的直接证据。

DMARC:声明对伪造邮件如何处置

DMARC 记录放在 _dmarc 子域,形式也是 TXT:

_dmarc.example.com.    IN TXT    "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; adkim=s; aspf=s"

字段含义对照:

字段 含义
v 版本,固定为 DMARC1,且必须排在最前
p 域策略:none 只观测,quarantine 进隔离/垃圾箱,reject 直接拒收
sp 子域策略,缺省继承 p
rua 聚合报告接收地址,定期收到「谁在冒用本域发信」的汇总
ruf 取证报告地址,含原始邮件样本,涉及隐私,多数场景不建议开启
adkim / aspf DKIM/SPF 对齐模式,s 为严格(域必须完全一致),r 为宽松(允许组织域匹配)
pct 策略适用百分比,历史字段,如今多数接收方按 100% 处理

DMARC 不产生结论,只消费结论

DMARC 自身不做任何认证。它读取 SPF 与 DKIM 的结果,检查这些结果的域是否与 From: 头域对齐,再按 p 处置。一个域如果只有 DMARC、没有 SPF 也没有 DKIM,能得到的唯一结论是「没有对齐的通过」。

对确实不发信的域来说,这恰好等价于全拒,所以 v=spf1 -allp=reject 是自洽组合。但对会发信的域,缺 SPF/DKIM 的 DMARC 只能一律拒绝:它无从区分你这边的合法发信源和外部的冒用者。此时 rua 报告虽然能列出所有来源 IP,但每一行都是失败,还需要人工辨认哪些是自己人。换句话说,没有对齐的认证结果,DMARC 记录只是把判定权交还给运气

为什么到处能见到 p=none

会发信的域通常先发布 v=DMARC1; p=none; rua=...,只观测不拦截:跑几周,从聚合报告里把真实的发信源(自建 MTA、事务邮件服务、协作平台等)逐个收进 SPF 或配置 DKIM 签名,确认报告里不再有未知来源,再依次升级到 quarantinereject。这个「先观测再收紧」的流程是 p=none 最常见的存在理由,不代表配置者偷懒。

不发信的域不需要这段流程,可以直接从 p=reject 起步。

CAA:限定谁能为你签发证书

example.com.    IN CAA    0 issue "<CA 域名>"
example.com.    IN CAA    0 issuewild "<CA 域名>"
example.com.    IN CAA    0 iodef   "mailto:security@example.com"

CAA 记录三个字段:flag、tag、value。flag 目前只定义了 0128(后者表示「关键」,不认识的 tag 应导致拒绝签发);tag 有 issue(允许签发的 CA)、issuewild(允许签发通配符证书的 CA)、iodef(违规举报地址)。签发前查询 CAA 是 CA 的强制义务,不符就应拒绝。CAA 在父域上默认对子域生效,子域自带 CAA 时以子域为准。

它防的是「CA 被诱导或误操作,为你的域签发证书」这一类问题:比如有人用社会工程让某个 CA 为你的域出证,或某个验证流程有缺陷。它不防你的主机被入侵,也不防已签发的证书被滥用。因此 CAA 属于纵深加固项,而不是日常防线——没有 CAA 只是回到默认状态(任何 CA 都可签发),本身不算漏洞。

其他记录与子域命名习惯

MX。 不接收邮件的域,最干净的做法是不留 MX,这本身就传达了「本域不收信」。如果希望更显式,可以发布 null MX(RFC 7505):

example.com.    IN MX    0 .

它表示本域明确声明不接受邮件,避免接收方按回退规则去找 A 记录投递。

子域命名。 这是最容易被忽略的一条泄露。用产品名当子域,等于把自建服务的清单和指纹直接公开:

grafana.example.com.    IN A    203.0.113.10
gitlab.example.com.     IN A    203.0.113.10
panel.example.com.      IN A    203.0.113.10

扫描者不需要任何入侵手段,读一遍 DNS 就知道你在跑监控、代码托管和一个面板;面板类产品的默认路径与子域命名相当固定,指向性更强。中性命名能减少这类信息,但不要把它当作主要防线:HTTPS 子域的域名会进入公开的证书透明度日志,通配符证书之外的具体子域几乎必然被记录、被聚合、被搜索引擎二次收录。命名习惯影响的是被发现成本,不是被发现的可能性。

检查序列与判读

#!/bin/sh
D=example.com
dig +short TXT "$D"           # 根域 TXT:SPF 与各类验证记录
dig +short TXT "_dmarc.$D"    # DMARC 策略
dig +short CAA "$D"           # 证书签发授权
dig +short A "$D"             # 是否存在 IPv4 端点
dig +short MX "$D"            # 是否接收邮件

每项结果的含义:

查询 有值
根域 TXT 至少声明了发信策略或持有某项服务 未声明任何策略,是「新域/未配置」的典型特征
_dmarc TXT 有明确处置策略,可读 p= 判断强度 接收方只能自行猜测,冒用几乎没有成本
CAA 签发被限定到指定 CA 任何 CA 均可签发,属默认状态,非缺陷
A / AAAA 存在 Web 端点 该名不提供 HTTP 服务,与域是否可疑无关
MX 声明了收信路径 明确不收信(null MX 表达同一意思)

两个判读陷阱:

可操作要点

对一个不发邮件、只提供 Web 的自用域,一套自洽且低维护的组合是:

example.com.            IN A     203.0.113.10
example.com.            IN TXT   "v=spf1 -all"
_dmarc.example.com.     IN TXT   "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"
example.com.            IN CAA   0 issue "<CA 域名>"

要点在于自洽:声明不发信,就不要保留任何发信路径;声明 p=reject,就不要缺少支撑它的 SPF。反过来,如果这个域将来要用来发信,必须先把发信源写进 SPF 或配上 DKIM,再上 p=reject,否则自己的邮件会和外部的伪造邮件一起被拒。

做完之后用上面那段脚本复查一遍:根域 TXT 应输出 v=spf1 -all_dmarc 应输出含 p=reject 的字符串,CAA 与 A 按需,MX 为空。任何一项空白,都还留着一条「这个域不像在正经使用」的可读信号。

← 全部文章