PAYATHON 2026

aiosmtpd 如何在不依赖 SPF、DMARC 或 DKIM 时识别伪造的 From:?

排障赵师傅

在这些限制下,无法可靠验证 From:

如果不使用 SPF、DKIM、DMARC,也不要求发件人或其服务器提供其他可信的身份凭据,就无法仅凭收到的邮件可靠判断 From: 是否被伪造。aiosmtpd 也没有接口能补足这项信息。

地址白名单只能确认邮件声称来自允许的地址。攻击者也能填写同一个地址,因此通过白名单检查并不能确认发件人身份。

逐一调查发件服务器、记录历史 IP 或反复检查 MAIL FROM,有助于排查异常,却无法补上缺失的身份证明。如果必须自动处理经过身份验证的来信,就需要放宽至少一项限制,引入可信的认证方式。

为什么检查邮件头和信封还不够

信息含义能否单独证明 From: 真实
邮件头 From:邮件声明的作者地址不能,发送方可以填写
SMTP MAIL FROM信封发件地址,通常用于退信不能,未验证时同样可以填写
连接 IP直接连接到接收服务器的主机地址不能,只能识别当前连接来源
EHLO 名称SMTP 客户端声明的主机名不能,属于客户端声明

例如,攻击者可以在 SMTP 会话中使用:

MAIL FROM:<alice@example.com>

同时在邮件头中填写:

From: Alice <alice@example.com>

两者完全一致,仍然不能证明发件人身份。SMTP 允许发送方提供这些字段,协议的安全说明也讨论了邮件伪造和传输层身份验证的局限。RFC 5321 §7.1

IP 白名单也有局限:同一邮件服务商的出站服务器可能服务很多用户,确认邮件来自该服务商,无法进一步确认它来自指定邮箱。域名的 MX 记录主要指向接收邮件的服务器,也不能直接当作出站服务器清单。

是否需要逐一调查 SPF、DKIM、DMARC

如果决定使用这些机制,通常应对每封邮件自动验证,并根据 DNS 缓存和 TTL 更新相关记录,无须人工维护每个邮箱的服务器清单。

这些机制验证的对象不同:

  • SPF 检查连接来源是否获准使用相应的 SMTP 身份,通常是 MAIL FROM 域,并不直接验证邮件头中的 From:。RFC 7208
  • DKIM 验证签名及其覆盖的内容,并确认签名域;签名域不一定与 From: 域相同。
  • DMARC 检查 SPF 或 DKIM 的验证结果是否与 From: 域对齐。SPF 或 DKIM 只要有一条路径验证通过且对齐,就可以使 DMARC 通过。DMARC 规范

即使 DMARC 通过,验证范围仍主要是域名。它不会验证地址中 @ 前面的本地部分,也不能证明某个具体的人亲自发送了邮件。DMARC 的验证范围

在接收时:

  • 可以直接取得当前的 MAIL FROM 值,不必事先调查所有可能的值。
  • 不应要求 MAIL FROM 与 From: 完全相同。合法的退信处理、代发等场景可能使用不同地址。
  • 如果对方没有提供可用的认证信息,增加查询次数也无法将邮件认定为“已验证”。

接收策略还应区分“无法验证”和“已证明是伪造邮件”,前者不能作为后者的结论。

可以采用哪些处理方式

保持现有发信方式,接受身份无法确定的结果

如果发件人、服务器和发送流程都不能改变,就只能结合域认证结果、连接来源、历史行为和内容判断风险。

符合白名单但缺乏可信认证的邮件,可以暂存或转人工确认。邮件涉及的业务操作应在身份确认后再执行。如果严格要求身份验证,也可以拒绝这类邮件,但这会同时拒绝部分合法来信。

要求发件人直接向你的服务认证

如果能够调整发信配置,可以让发件人直接连接你的提交服务,通过 SMTP AUTH 登录,再由服务器检查登录身份是否有权使用指定的 From:。

aiosmtpd 提供认证框架,凭据验证和发件地址授权仍需自行实现。认证成功不会自动限制用户可以填写哪些 From: 地址。aiosmtpd Authentication System

这里验证的是直接连接到你的服务的客户端。用户在其邮件服务商那里登录过,你的接收服务器也不会因此自动获得可验证的登录身份。

使用签名或独立的业务确认

如果允许调整流程,还可以采用以下方式:

  • 使用 S/MIME 或 OpenPGP 签名,通过可信渠道建立签名密钥与允许地址的对应关系。
  • 暂存邮件,要求用户登录你的系统,确认该封邮件对应的具体操作。

验证签名时,必须检查签名者身份、受保护的内容,以及这些内容与业务请求的关系。仅检查邮件里有没有签名附件是不够的。S/MIME 提供数字签名能力,密钥与地址之间的信任关系仍需正确建立。S/MIME 规范

这些方式都需要新增凭据或交互,因此需要放宽原有的限制。

aiosmtpd 示例:白名单过滤后暂存待验证

邮件头需要在收到 DATA 内容后检查。在 handle_DATA() 中,envelope.original_content 提供原始邮件字节,envelope.mail_from 提供 SMTP 信封发件地址。aiosmtpd handlers 文档

下面的 handler 检查发件地址是否在白名单中,通过后将邮件交给待验证存储,不执行邮件请求的业务操作:

from email import policy
from email.errors import HeaderParseError
from email.parser import BytesHeaderParser


ALLOWED_FROM = {
    ("alice", "example.com"),
    ("bob", "example.org"),
}


class AllowlistHandler:
    def __init__(self, save_pending):
        # 异步回调:返回前必须已可靠保存待验证邮件。
        self.save_pending = save_pending

    async def handle_DATA(self, server, session, envelope):
        raw = envelope.original_content

        try:
            message = BytesHeaderParser(
                policy=policy.default
            ).parsebytes(raw)

            fields = message.get_all("From", [])
            if message.defects or len(fields) != 1:
                return "550 5.7.1 Exactly one valid From header required"

            header = fields[0]
            if header.defects or len(header.addresses) != 1:
                return "550 5.7.1 A single From address is required"

            if any(g.display_name is not None for g in header.groups):
                return "550 5.7.1 From groups are not accepted"

            address = header.addresses[0]
        except (ValueError, HeaderParseError):
            return "550 5.7.1 Invalid From header"

        identity = (address.username, address.domain.lower())
        if identity not in ALLOWED_FROM:
            return "550 5.7.1 From address is not allowed"

        await self.save_pending(
            raw=raw,
            claimed_from=identity,
            mail_from=envelope.mail_from,
            peer=session.peer,
            identity_verified=False,
        )
        return "250 2.0.0 Accepted for pending review"

save_pending 是需要由应用提供的异步存储回调,不是 aiosmtpd 内置 API。返回 250 前,必须可靠保存邮件;如果存储暂时失败,应用应将其转换为适当的 451 临时错误。

示例使用 Python 的结构化地址解析接口,检查重复字段、解析缺陷和地址数量。这里采用的本地策略是只接受一个 From: 字段和一个邮箱地址,可能拒绝部分协议允许的复杂形式。Python 3.12 邮件头解析文档

返回 250 表示邮件已进入待验证状态,身份尚未确认。攻击者填写白名单地址,同样可以通过这段代码的地址检查。

实施时的注意事项

不要用显示名称、字符串包含关系或简单正则匹配白名单。例如:

From: "alice@example.com" <attacker@example.net>

这里的邮箱地址是 attacker@example.net。示例按解析后的邮箱地址匹配,只对域名转小写;本地部分的大小写、别名及国际化地址,应按已确认的规则处理。

外部邮件携带的 Authentication-Results: 可能被攻击者伪造,不能直接信任。只有来自你控制或明确可信的验证系统,并经过可信接收链路传入的结果,才应参与判断。RFC 8601 §7.1

STARTTLS 保护 SMTP 连接,但不会自动证明邮件作者身份。服务器间 TLS 与端到端的发件人认证需要分别考虑。SMTP STARTTLS 规范

对于用途有限、会自动执行操作的服务器,没有可信身份依据的邮件应保持“待验证”状态,不能仅凭白名单命中就执行邮件请求的操作。

备注:内容仅供参考。