没有不值得去解决的问题,也没有不值得去学习的技术!

一次域名安全评分修复实录:从 71 分到 83 分,真正的问题竟然是 SPF

【图7:修复 SPF 后,Gmail 实际验证 SPF、DKIM、DMARC 全部 PASS】

作者:

在

最近我用 TCPLab 的“域名综合检测”检查了一下自己的主域名:

www.shuijingwanwq.com

最开始只是想看看域名、DNS、邮件认证、HTTPS/TLS 等配置有没有什么明显问题。

第一次检测的结果是:

71 分,B 级。

原本以为这只是一次比较普通的网站配置检查,没想到后面还发现了一个挺有意思的问题:

TCPLab 没有检测到 DKIM,但 DKIM 实际一直正常;反而是表面上看起来已经存在的 SPF 配置,在真实邮件投递时出现了 PERMERROR。

最后,我把这次真正有必要处理的问题修掉以后,评分从 71 分提升到了 83 分。

至于 DNSSEC、CAA、CSP 等剩余项目,我没有为了追求更高分数继续全部处理。不是因为这些配置完全没有价值,而是其中一些属于进一步的安全加固,当前处理的必要性并不高。为了多拿几分增加配置复杂度,甚至引入新的故障风险,我觉得并不划算。

另外,这次也再次说明了一点:

检测工具给出的建议值得参考,但不能机械地全部照做。

有些问题确实需要修;有些只是可选的安全增强;还有一些,则可能是检测工具本身没有识别到实际配置。

下面完整记录这次过程。


一、第一次检测:71 分

最开始的 TCPLab 检测结果是:

71 分,B 级。

【图1:TCPLab 第一次检测,域名综合评分为 71 分】
【图1:TCPLab 第一次检测,域名综合评分为 71 分】

报告里主要提示了这些问题:

  • 域名即将到期;
  • HTTP 没有强制跳转 HTTPS;
  • DMARC 缺失;
  • DKIM 未检测到;
  • DNSSEC 未开启;
  • CAA 缺失;
  • Web 安全响应头不完整。

这里我没有把所有提示都当成“必须马上修复的问题”。

我的处理思路主要分成两类:

第一类是确实影响当前使用或安全基础的项目,比如域名续费、HTTP → HTTPS、邮件认证。

第二类是有安全价值,但当前优先级没那么高的增强项,比如 DNSSEC、CAA,以及部分 Web 安全响应头。

这样处理主要是为了把精力放在真正必要的问题上,而不是为了提高检测评分增加一堆当前收益有限的配置。

至于检测结果本身是否完全准确,则是后面在排查 SPF 和 DKIM 时才进一步发现的问题。


二、域名续费:这一项检测是准确的

检测时,域名确实只剩十几天就要到期了。

其实前段时间我已经收到过域名续费提醒邮件,只不过因为最近事情比较多,一直还没有抽出时间处理。

所以 TCPLab 这一项并不是误报。

看到检测结果以后,我也把续费这件事提到了最高优先级,并在随后完成了域名续费。

相比 DNSSEC、CAA、CSP 等安全配置,域名续费其实才是最基础的一项。

毕竟:

DNSSEC、CAA、CSP 配置得再完善,如果域名忘记续费,一切都没有意义。

所以这次检测至少提醒我及时把这件已经知道、但还没有处理的事情真正做完了。


三、HTTP → HTTPS:原本长期正常,不知道什么时候被改坏了

接下来我检查:

Plaintext
curl -I http://www.shuijingwanwq.com/

结果返回:

Plaintext
HTTP/1.1 200 OK

也就是说:

Plaintext
http://www.shuijingwanwq.com/

当时竟然可以直接返回页面,而不是跳转到 HTTPS。

这一点其实让我有些意外。

因为我的博客已经运行十多年,HTTP → HTTPS 的 301 跳转很早以前就是正常配置。

所以这并不是一个长期遗留的基础错误。

更大的可能是后来某次修改 Nginx 配置时,不小心把原来的 HTTP 301 跳转逻辑改掉了,只是我一直没有注意到。

HTTPS 本身一直是正常的,TLS 和 HSTS 也都没有问题。

真正异常的是:

HTTP 请求不再跳转,而是直接返回了 200。

这次我没有再回头修改 Nginx。

因为网站前面已经有 CDN / 边缘加速服务,用 CDN 层直接完成 HTTP → HTTPS 跳转会更简单,也更直接。

我的主站使用腾讯云 EdgeOne,因此进入:

站点加速 → HTTPS → 强制 HTTPS

开启全局强制 HTTPS,并选择:

Plaintext
301

作为重定向方式。

【图2:在腾讯云 EdgeOne 中开启强制 HTTPS,并使用 301 重定向】
【图2:在腾讯云 EdgeOne 中开启强制 HTTPS,并使用 301 重定向】

保存以后重新测试:

Plaintext
curl -I http://www.shuijingwanwq.com/

结果变成:

Plaintext
HTTP/1.1 301 Moved Permanently
Location: https://www.shuijingwanwq.com/

我还顺便检查了几个其他子域名:

Plaintext
curl -I http://go-dev.shuijingwanwq.com/
curl -I http://assets-go-dev.shuijingwanwq.com/
curl -I http://ja-go-dev.shuijingwanwq.com/

也都能够正常跳转到 HTTPS。

相比再去追查 Nginx 到底是哪次改动影响了原有配置,直接在 CDN 层统一处理更加简单。

这项修复就到这里结束了。


四、补上 DMARC

接下来处理邮件认证。

我的 shuijingwanwq.com 邮箱使用腾讯企业邮箱。

MX 记录是:

Plaintext
5 mxbiz1.qq.com.
10 mxbiz2.qq.com.

先检查 DMARC:

Plaintext
dig TXT _dmarc.shuijingwanwq.com +short

没有任何输出。

说明当时确实没有配置 DMARC。

于是我在 Cloudflare DNS 中新增:

Plaintext
类型:TXT
名称:_dmarc
内容:v=DMARC1; p=none
【图3:在 Cloudflare DNS 中新增 DMARC 记录】
【图3:在 Cloudflare DNS 中新增 DMARC 记录】

保存后验证:

Plaintext
dig TXT _dmarc.shuijingwanwq.com +short

已经能够得到:

Plaintext
"v=DMARC1; p=none"

这里我没有直接使用:

Plaintext
p=quarantine

或者:

Plaintext
p=reject

而是先使用:

Plaintext
p=none

原因是我想先确认自己当前所有合法邮件的 SPF / DKIM 是否正常。

刚开始配置 DMARC 时直接使用严格策略没有太大必要。

而后面的排查也证明,这个谨慎是值得的,因为我的 SPF 当时确实存在一个问题。


五、TCPLab 没检测到 DKIM,于是我决定用真实邮件验证

TCPLab 当时显示:

DKIM 未检测到。

我首先想到的是:

腾讯企业邮箱可能已经正常使用 DKIM,只是 TCPLab 不知道具体的 selector,因此没有查询到对应记录。

因为 DKIM 通常不是查询:

Plaintext
_domainkey.example.com

而是:

Plaintext
selector._domainkey.example.com

如果不知道 selector,扫描器并不一定能够发现对应的 DKIM 记录。

与其猜 selector,我觉得更直接的方式是:

真正发一封邮件。

于是我使用:

Plaintext
admin@shuijingwanwq.com

从腾讯企业邮箱发送测试邮件到 Gmail。

然后在 Gmail 中打开:

更多 → 显示原始邮件

结果一下子发现了真正的问题。


六、第一次真实邮件验证:真正出问题的是 SPF

Gmail 给出的认证结果是:

Plaintext
SPF: PERMERROR
DKIM: PASS
DMARC: PASS
【图4:第一次 Gmail 实际邮件验证,SPF 为 PERMERROR,而 DKIM 和 DMARC 都是 PASS】
【图4:第一次 Gmail 实际邮件验证,SPF 为 PERMERROR,而 DKIM 和 DMARC 都是 PASS】

这个结果很有意思。

原本我主要在检查 TCPLab 提示的 DKIM。

但真正负责接收这封邮件的 Gmail 却告诉我:

DKIM 是正常的,SPF 才有问题。

这也是这次排查过程中最值得记录的地方之一。

在线检测工具可以帮助发现线索,但真实邮件投递提供了更加直接的验证结果。


七、DKIM 实际一直正常

继续查看 Gmail 原始邮件,可以找到:

Plaintext
DKIM-Signature:

其中明确包含:

Plaintext
d=shuijingwanwq.com;
s=xacy2606;
【图5:Gmail 原始邮件中的 DKIM-Signature,确认 selector 为 xacy2606】
【图5:Gmail 原始邮件中的 DKIM-Signature,确认 selector 为 xacy2606】

也就是说,这封邮件使用的是:

Plaintext
DKIM 域名:shuijingwanwq.com
selector:xacy2606

再回到 Cloudflare DNS,也确实能够看到:

Plaintext
xacy2606._domainkey.shuijingwanwq.com

对应的 DKIM TXT 记录。

所以可以确认:

腾讯企业邮箱的 DKIM 一直都是正常工作的。

TCPLab 所谓的“DKIM 未检测到”,只是没有识别到当前实际使用的 selector:

Plaintext
xacy2606

因此这一项没有必要为了让检测结果变成绿色而继续修改。


八、真正的问题:SPF 写错了一处

Gmail 原始邮件中,SPF 给出了:

Plaintext
spf=permerror

并且明确提示:

Plaintext
mail.qq.com not found

这时我再回头检查原来的 SPF:

Plaintext
v=spf1 include:mail.qq.com ~all

问题就比较清楚了。

我当时写的是:

Plaintext
include:mail.qq.com

结合 Gmail 返回的错误,以及当前腾讯企业邮箱对应的 SPF 配置,最终确认我这个场景应该使用:

Plaintext
include:spf.mail.qq.com

所以我把 SPF 修改为:

Plaintext
v=spf1 include:spf.mail.qq.com ~all

这里需要特别说明:

这只是我当前腾讯企业邮箱配置下实际验证通过的结果。

不同腾讯邮件产品、历史配置或者其他发送服务,SPF 内容不一定完全相同,不能看到这篇文章后就机械照抄。

真正应该参考的,还是自己实际使用的邮件服务配置,并通过真实投递进行验证。

另外,这里我是修改原有 SPF TXT 记录,而不是新增第二条 SPF。

因为同一个域名存在多条 SPF 记录,反而可能导致新的 PermError。

修改以后,Cloudflare 中已经可以看到:

Plaintext
v=spf1 include:spf.mail.qq.com ~all

同时原来的 DKIM 记录:

Plaintext
xacy2606._domainkey.shuijingwanwq.com

也仍然存在。

【图6:Cloudflare 中修正后的 SPF,以及原本已经存在的 DKIM 记录】
【图6:Cloudflare 中修正后的 SPF,以及原本已经存在的 DKIM 记录】

通过命令再次确认:

Plaintext
dig TXT shuijingwanwq.com +short

结果是:

Plaintext
"v=spf1 include:spf.mail.qq.com ~all"
"google-site-verification=..."

但经历过前面的误判以后,我已经不打算只看到 DNS 正确就认为事情结束了。

所以继续做第二次真实邮件测试。


九、第二次发送邮件:SPF、DKIM、DMARC 全部 PASS

修改 SPF 后,我再次从:

Plaintext
admin@shuijingwanwq.com

给 Gmail 发送一封新邮件。

重新进入:

显示原始邮件

这一次结果已经变成:

Plaintext
SPF: PASS
DKIM: PASS
DMARC: PASS
【图7:修复 SPF 后,Gmail 实际验证 SPF、DKIM、DMARC 全部 PASS】
【图7:修复 SPF 后,Gmail 实际验证 SPF、DKIM、DMARC 全部 PASS】

到了这里,我才认为邮件认证问题真正处理完成。

最终结果:

项目结果
SPFPASS
DKIMPASS
DMARCPASS

其中 DKIM 从头到尾其实都没有问题。

真正修改的是 SPF:

Plaintext
include:mail.qq.com

改为:

Plaintext
include:spf.mail.qq.com

之后 Gmail 的真实认证才从:

Plaintext
SPF: PERMERROR

变成:

Plaintext
SPF: PASS

十、为什么 SPF 出错时,DMARC 仍然能够 PASS?

第一次测试时还有一个容易让人困惑的地方:

Plaintext
SPF: PERMERROR
DKIM: PASS
DMARC: PASS

为什么 SPF 有问题,DMARC 还是通过了?

因为 DMARC 并不要求:

Plaintext
SPF PASS
并且
DKIM PASS

DMARC 可以通过符合要求的 SPF 或 DKIM 路径完成验证,同时还需要满足域名对齐。

我的这封邮件中:

Plaintext
DKIM: PASS
d=shuijingwanwq.com

而发件人 From 使用的域名也是:

Plaintext
shuijingwanwq.com

因此 DKIM 已经满足了 DMARC 所需要的认证和域名对齐条件。

所以即使 SPF 当时出现:

Plaintext
PERMERROR

DMARC 仍然可以:

Plaintext
PASS

这也说明:

只看到 DMARC PASS,并不能反推出 SPF 和 DKIM 两项一定都正常。

最好还是分别检查。


十一、重新检测:从 71 分提高到 83 分

真正的问题处理完成以后,我再次运行 TCPLab。

新的综合评分变成:

83 分。

仍然是 B 级,但已经比最开始的:

Plaintext
71 分

提高了:

Plaintext
12 分
【图8:修复后重新运行 TCPLab,综合评分从 71 提升到 83】
【图8:修复后重新运行 TCPLab,综合评分从 71 提升到 83】

这次已经可以看到:

  • HTTP → HTTPS 强制跳转被识别;
  • SPF 已经正常;
  • DMARC 已经存在;
  • HTTPS / TLS 正常;
  • HSTS 正常。

但 TCPLab 仍然显示:

DKIM 未检测到。

这时我已经没有继续处理它。

因为真实邮件投递已经明确证明:

Plaintext
DKIM: PASS

并且可以从原始邮件和 DNS 两边互相对应:

Plaintext
d=shuijingwanwq.com
s=xacy2606

以及:

Plaintext
xacy2606._domainkey.shuijingwanwq.com

所以这种情况下,真实投递结果比扫描器是否能够找到 selector 更有参考意义。


十二、我也回到原来的帖子补充了更正

在这次排查之前,我曾经在一个帖子里讨论过这份 TCPLab 检测结果。

当时因为还没有做真实邮件验证,对 SPF / DKIM 的判断并不完整。

后来确认真实情况以后,我又回到原帖,把:

  • SPF 实际出现 PERMERROR;
  • 正确修改后的 SPF;
  • DKIM 实际一直 PASS;
  • Gmail 最终三项全部通过;

这些后续结果补充了进去。

【图9:在原帖补充真实邮件验证结果,并更正之前不完整的判断】
【图9:在原帖补充真实邮件验证结果,并更正之前不完整的判断】

我觉得这一步还是有必要的。

技术记录中,一开始判断不够准确很正常。

关键是后来拿到新的证据以后,应该及时把旧结论补充完整。

这比强行维持最初的判断更重要。


十三、为什么我没有继续为了分数处理所有剩余项目?

重新检测以后仍然还有一些扣分项。

例如:

  • DNSSEC;
  • CAA;
  • 部分 Web 安全响应头;
  • 更严格的 DMARC 策略;
  • TCPLab 仍然没有检测到的 DKIM。

我没有继续全部处理。

原因并不是这些东西毫无价值。

而是:

它们当前的处理必要性,与刚才真正修掉的问题并不在同一个级别。

例如 HTTP → HTTPS 返回错误状态,或者 SPF 配错,这属于已经存在、可以明确验证的问题。

而 DNSSEC、CAA、CSP 更多是在当前正常运行的基础上进一步增强安全性。

为了把 83 分继续刷高,就增加额外配置、维护成本,甚至潜在故障风险,我觉得没有必要。

所以这次就停在了这里。


十四、DNSSEC:有价值,但不是当前必须处理

DNSSEC 可以为 DNS 数据提供额外的真实性和完整性验证。

这当然是一项有价值的安全能力。

但它通常还涉及 DNS 服务商与域名注册商之间的 DS 记录配置。

如果配置错误,严重时可能直接影响域名解析。

所以我不会为了检测报告里的一项扣分就临时开启。

如果以后决定配置 DNSSEC,我更倾向于把它作为一次独立任务完整验证,而不是顺手打开一个开关。


十五、CAA:可以做,但没有必要现在做

CAA 用来限制哪些 CA 可以为域名签发证书。

它同样有安全价值。

不过没有 CAA 并不意味着当前 HTTPS 证书不安全,也不代表网站存在故障。

而我的网站还同时涉及 Cloudflare、EdgeOne 等服务。

如果未来要配置 CAA,更合理的做法是先确认实际证书签发和自动续期方式,再设置对应规则。

为了提高几分直接照抄 CAA 记录,并不是我想采用的方式。


十六、Web 安全响应头:不能为了评分机械增加

报告里还有:

Plaintext
Content-Security-Policy
X-Content-Type-Options
X-Frame-Options
Referrer-Policy

等响应头建议。

其中有些以后确实值得优化。

但尤其像:

Plaintext
Content-Security-Policy

如果网站同时使用:

  • WordPress;
  • Google 广告;
  • 第三方统计;
  • 外部 JavaScript;
  • CDN;
  • 外部图片和字体;

那么 CSP 配置不正确,很容易直接造成页面功能异常。

所以我不会为了把检测结果中的:

Plaintext
0/5

变成:

Plaintext
5/5

就一次性增加所有响应头。

这些以后可以单独审查。

当前没必要为了评分增加额外风险和工作量。


十七、DMARC 暂时继续保持 p=none

现在我的 DMARC 是:

Plaintext
v=DMARC1; p=none

也就是先启用认证和观察。

以后确认所有合法邮件来源长期稳定以后,可以再考虑:

Plaintext
p=quarantine

甚至:

Plaintext
p=reject

但我刚刚才通过真实邮件发现并修复了 SPF 配置错误。

这个时候马上把 DMARC 提升到最严格策略没有太大必要。

先稳定运行一段时间再说。


十八、这次真正有价值的不是 71 分变成 83 分

如果只看结果:

Plaintext
71 → 83

那么这可能只是一篇很普通的“网站安全评分优化记录”。

但我觉得真正值得记录的,并不是这 12 分。

而是这次验证方式带来的差异。

在线检测让我注意到了:

Plaintext
DKIM 未检测到

但真实邮件告诉我:

Plaintext
SPF: PERMERROR
DKIM: PASS
DMARC: PASS

然后修复真正有问题的 SPF 后:

Plaintext
SPF: PASS
DKIM: PASS
DMARC: PASS

而此时 TCPLab 仍然没有检测到 DKIM。

这其实很好地说明了一件事:

自动化检测工具适合作为问题线索,但不适合作为唯一的事实来源。

尤其是邮件认证。

很多时候最直接的验证办法并不复杂。

真的发一封邮件。

然后看看真正负责接收邮件的服务器如何认证。


十九、以后再检查邮件认证,我会这样做

经过这次以后,如果以后再检查一个域名的邮件认证,我大概会按这个顺序来。

先看 DNS:

Plaintext
dig MX example.com +short
dig TXT example.com +short
dig TXT _dmarc.example.com +short

然后真正从这个域名邮箱发送一封邮件到 Gmail。

在 Gmail 中打开:

更多 → 显示原始邮件

首先看:

Plaintext
SPF
DKIM
DMARC

如果其中有异常,再检查:

Plaintext
Authentication-Results:

以及:

Plaintext
DKIM-Signature:

DKIM 里重点看:

Plaintext
d=
s=

其中:

Plaintext
d=

代表 DKIM 签名域名。

Plaintext
s=

就是 selector。

有了 selector 以后,才知道真正应该查询:

Plaintext
selector._domainkey.example.com

而不是仅仅因为某个在线工具显示:

Plaintext
DKIM 未检测到

就马上认定 DKIM 没有配置。


二十、最终结果

这次实际处理完成以后,目前状态是:

项目当前状态
域名续费检测后完成续费
HTTP → HTTPS通过 EdgeOne 恢复 301 强制跳转
HTTPS / TLS正常
HSTS正常
SPF已修正,Gmail 实测 PASS
DKIM原本正常,Gmail 实测 PASS
DMARC新增 p=none,Gmail 实测 PASS
DNSSEC暂不处理
CAA暂不处理
CSP 等响应头暂不为了评分增加配置

TCPLab 综合评分:

Plaintext
初始:71 分
最终:83 分

但相比这个:

Plaintext
83 分

我更在意的最终结果其实是:

Plaintext
SPF:   PASS
DKIM:  PASS
DMARC: PASS

因为这是一次真实邮件投递以后,真正负责收信的 Gmail 给出的认证结果。


最后

这次经历让我再次感觉到:

安全检测工具很有价值。

它可以快速告诉我们:

哪些地方值得检查。

但它不能完全替代我们判断:

哪些问题真正需要解决。

有些项目确实坏了,应该马上修。

有些属于进一步加固,目前没必要为了评分增加额外工作量。

还有一些,则可能只是检测工具自己没有识别出来。

所以最终我没有继续追求所谓的:

Plaintext
100 分

而是在确认:

  • 域名已经完成续费;
  • HTTP 已重新恢复到 HTTPS 的 301 跳转;
  • TLS 正常;
  • SPF 正常;
  • DKIM 正常;
  • DMARC 正常;

以后停了下来。

把真正的问题解决掉,比把检测页面上的每一项都刷成绿色更重要。

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系