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 规范
对于用途有限、会自动执行操作的服务器,没有可信身份依据的邮件应保持“待验证”状态,不能仅凭白名单命中就执行邮件请求的操作。
备注:内容仅供参考。
