升级后你的邮件收件箱全空: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,但那等于接受伪造风险。