升级后你的邮件收件箱全空:Hermes 不再相信发件人自己写的验证结果

你让 Hermes 接管你的收件箱:日报邮件进来,它读完、总结、写进记忆。升级之后一切安静得可怕——一封都没进来,连你白名单里的发件人也没有。日志里只有一行 WARNING,提到一个你从没配过的参数。
问题出在“这封邮件是谁发的”这件事上。Hermes 会检查邮件头里的 Authentication-Results,确认发件人身份;而这一行恰好是发件人可以自己写的。当你的收件服务器没有盖自己的章时,攻击者伪造的那行就排在最上面,Hermes 把它当成了权威结论。这次修复的态度很干脆:没有锚点,就不相信。
伪造是怎么成立的
邮件头里的 Authentication-Results 是给“这封邮件过了 SPF / DKIM / DMARC”背书的。正常情况下,它由你的收件服务器在收到邮件后加上——因为服务器已经完成了验证,它写的结果才可信。
问题在于:这个头是普通文本,发件人可以自己塞一行 attacker.self; dmarc=pass header.from=allowed.example 进来。如果接收方是自建的 MTA,而它没有用 OpenDKIM / OpenDMARC 之类给自己盖一个 authserv-id,那么这一行就会留在最上面。Hermes 旧的行为是“取最上面那一行,不管它自称是谁写的”,于是:
- 攻击者把
From:伪造成你白名单里的地址; - 他自己写一行
dmarc=pass; - Hermes 认为验证通过,投递。
修复后的 _verify_sender_authentication 只认两种结果——取最顶部的 Authentication-Results,并且它的 authserv-id 必须精确等于你配置的那个:
if not authserv_id: # without a pin the topmost header may be one the sender wrote
return False, _MISSING_AUTHSERV_REASON
if clauses[0].strip().lower() != authserv_id:
return False, _UNTRUSTED_AUTHSERV_REASON
顺带收紧的还有两处:一个 dmarc 结果出现两次会直接被判为 ambiguous dmarc result(不再猜哪个算数);dmarc=pass 里的 header.from 必须和你解析出来的 From: 域一致,不一致就不认。
升级后到底会发生什么
这是一次行为变更,受影响范围要说清楚:
- 谁受影响:开启了发件人验证、但没配置 authserv-id 的部署。注意验证是默认开启的,包括开了
EMAIL_ALLOW_ALL_USERS/GATEWAY_ALLOW_ALL_USERS这类“允许所有人”的部署。 - 会发生什么:升级后,所有需要“已认证发件人”的邮件都会被丢弃,白名单里的发件人也不例外,直到运维做下面两件事之一——设置
EMAIL_AUTHSERV_ID(或配置platforms.email.authserv_id),或者显式放弃这项验证。 - 你会看到什么:每个没配 pin 的账号在连接时会打一条 WARNING,提示 authserv-id 未配置、拒绝相信
Authentication-Results,并给出两个选项(设置 authserv-id,或把require_authenticated_sender设为 false、或设置EMAIL_TRUST_FROM_HEADER=true)。账号第一次连接失败、之后被重连看守进程拉起时,这条警告同样会出现。 - 谁不受影响:已经配好 pin 的部署,以及本来就关掉验证的部署,行为和以前完全一样。
怎么找到要填的那个值
不要猜。打开一封你的服务器投递过来的邮件,看原始内容(Gmail 的“显示原始邮件” / “Show original”),找到最顶部那条 Authentication-Results 头,取第一个分号之前的那段 token。
- Gmail 的情况是
mx.google.com; - 如果这个 token 在不同邮件之间会变,说明它是每台中继的主机名。这时正确做法是让你的 MTA 盖一个稳定的 authserv-id(比如 OpenDKIM 的
AuthservID、OpenDMARC 的对应配置),然后 pin 那个值。
配置方式二选一,环境变量优先便于容器部署:
# .env 或容器环境变量
EMAIL_AUTHSERV_ID=mx.google.com
# config.yaml
platforms:
email:
authserv_id: mx.google.com
# 保留默认的严格模式;只有在你能接受伪造风险时才关掉
require_authenticated_sender: true
值会被规整为小写比较,所以大小写不敏感;但域必须对得上,不要填成一个大范围的域来图省事。
如果你确实需要临时恢复收件
设置 platforms.email.require_authenticated_sender: false(或环境变量 EMAIL_TRUST_FROM_HEADER=true)会让 Hermes 重新接受未经认证的发件人。这是有意识的降级:在自建 MTA 上它意味着任何人都能伪造白名单发件人。更稳妥的替代方案是给 MTA 配一个固定的 authserv-id——一次性配置,长期不用再想这件事。
想了解同类身份伪造问题的其他修复,可以看发件人身份伪造的三处修复;邮件平台的其余配置见 Hermes 命令参考。
小结
Authentication-Results是发件人可以自己写的头,自建 MTA 不盖章时它可能排在最上面;- Hermes 现在要求 authserv-id 精确匹配,没配就拒收需要认证发件人的邮件,并打一条 WARNING;
- 取最顶部
Authentication-Results里第一个分号前的 token(Gmail 是mx.google.com)填进EMAIL_AUTHSERV_ID/platforms.email.authserv_id; - 临时退路是
require_authenticated_sender: false或EMAIL_TRUST_FROM_HEADER=true,但那等于接受伪造风险。