邮件安全攻防:从 SPF/DKIM/DMARC 绕过原理到授权演练框架

前言

邮件安全里有一类问题特别典型:你明明配置了 SPF 和 DKIM,邮件还是进了垃圾箱;或者反过来,有人伪造成你的域名发信,收件端却毫无察觉。这两件事的根源是同一个 —— 收件方到底凭什么信任一封邮件。

本文走两条线:一是自建邮件服务器的完整运维流程(环境、DNS 认证体系、Postfix 与 OpenDKIM 部署、投递验证);二是站在攻击者视角剖析邮件伪造链路的每个可利用点,并给出企业邮件安全授权的演练框架。理解攻击链才能做有效的防守,这是红蓝对抗中邮件方向的常见切入点。

授权边界声明

本文攻击链部分仅用于授权范围内的安全评估与企业邮件安全建设。所有测试必须在自有域名与自有服务器上进行,或已取得书面授权(企业内部安全意识演练须经安全部门审批并留存授权文件)。

未经授权伪造他人域名发信、搭建诱饵系统收集凭证,可能违反《网络安全法》《刑法》第 285/286 条等法律法规,并需承担相应法律责任。本文不提供可直接用于未授权场景的投递工具实现。

一、邮件信任链的整体架构

一封邮件从发出到被信任,要过三道关:

认证机制 验证对象 DNS 记录位置
SPF 发信 IP 是否被域名授权 TXT @
DKIM 邮件内容是否被篡改 TXT mail._domainkey
DMARC SPF/DKIM 失败后如何处理 TXT _dmarc

三者关系是递进的:SPF 管谁在发,DKIM 管内容真不真,DMARC 管对不齐时怎么办。

发信 → Postfix (127.0.0.1:25) → OpenDKIM 签名 → 收件方 MX
                ↓
        收件方依次校验 SPF → DKIM → DMARC → 决定收件箱/垃圾箱/拒收

1.1 从攻击者视角看这条链路

攻击者在邮件这个方向上,目标通常不是"攻破服务器",而是让一封伪造的信件成功抵达收件箱并取得信任。围绕这个目标,可利用点集中在三处:

环节 攻击者关注的问题 常见突破口
发信源 能不能以目标域名的名义发出信? SPF 未覆盖全部发信源、子域名缺口
内容可信度 信会不会被判为伪造? DKIM 宽松配置、签名规范化范围
处置策略 失败后收件方会不会拦截? DMARC 停在 p=none

这三处只要有任意一处存在缺口,伪造邮件的成功率就会显著上升。下面逐项展开。

二、攻击链剖析:SPF 的绕过空间

2.1 SPF 的判定逻辑与弱点

SPF 记录本质是"授权清单",收件方检查发信 IP 是否在清单内。它的弱点在于清单的表达能力有限:

v=spf1 ip4:203.0.113.10 include:_spf.example-cdn.com ~all

几个典型缺口:

  • 机制数量上限:SPF 规定 DNS 查询不得超过 10 次,超限直接判定 permerror。复杂架构(多家邮件服务商、多个 SaaS 发信平台)很容易踩到这个上限,运维往往被迫用 include 收窄,反而留下未覆盖的发信源。
  • include 范围过宽:引入第三方发信平台的 SPF 时,实际授权的是该平台的整个 IP 段。攻击者若能租用同一平台的相邻资源,就能从"被授权的 IP 段"内发信,SPF 校验照样 pass。
  • ~all 与 -all 的差异:~all(软失败)只是"标记为可疑",很多收件方对 softfail 并不拦截,只降权。攻击者伪造时命中 softfail,仍可能进入收件箱。
  • 子域名缺口:这是最普遍的一个。企业通常只给根域名配了 SPF,但 mail.公司域名、news.公司域名、info.公司域名 等子域往往没有记录。攻击者改用子域发信,绕过主域策略;而很多邮件客户端在展示时只显示根域名,收件人很难察觉。

2.2 检测思路

防守方要主动排查,而不是等出事:

# 排查 SPF 机制数量是否接近上限(含递归 include 展开)
dig +short TXT 你的域名
# 用在线工具展开 include 链,统计最终 DNS 查询次数,接近 10 就要重构

# 枚举子域名,逐个检查是否缺少 SPF/DMARC
# 重点关注:mail / smtp / news / info / marketing / 各业务子域
dig +short TXT mail.你的域名
dig +short TXT _dmarc.mail.你的域名

关键点:SPF 的策略必须覆盖所有可能被用于发信的子域。v=spf1 -all 这种"明确声明该子域不发信"的记录,比什么都不配要安全得多。

三、攻击链剖析:DKIM 的实现细节与利用

3.1 DKIM 签名的保护范围

DKIM 用私钥对邮件头部和正文做签名,收件方用 DNS 里的公钥验签。它的保护强度取决于两个配置项:

配置项 含义 安全性
Canonicalization 规范化方式(simple / relaxed) simple/simple 更严格但易因转发失效;relaxed/simple 是常见折中
OversignHeaders 对 From 等关键头部二次签名 配了才防头部注入
h= 头部列表 签名覆盖哪些头部 列表越全越安全

3.2 头部注入:不破解密钥也能伪造

这是 DKIM 最容易被忽略的问题。DKIM 签名时只覆盖 h= 标签里列出的头部。如果 h= 里没有 From,或者签名没有对 From 做 oversign,攻击者可以在已签名的邮件中追加一个伪造的 From 头部:

原始邮件(已签名):
  From: real@trusted-domain.com
  DKIM-Signature: ... h=from:subject:date; ...

攻击者追加头部后:
  From: ceo@your-company.com      ← 邮件客户端通常显示第一个或最后一个
  From: real@trusted-domain.com
  DKIM-Signature: ... h=from:subject:date; ...   ← 签名本身依然有效

收件方验签时,签名覆盖的那个 From 验证通过,但邮件客户端展示的可能是另一个。这解释了为什么必须在 OpenDKIM 配置里加 OversignHeaders From。

3.3 重放与签名复用

DKIM 签名本身不绑定收件人、不包含时效。一封合法签名的邮件被截获后,理论上可以被重放给其他收件人,签名依然有效。缓解手段:

  • 在 h= 中包含 To、Subject、Date 等,扩大签名覆盖面
  • 配合 DMARC 的严格对齐模式(adkim=s、aspf=s),要求 DKIM 的 d= 域与 From 域完全一致而非仅同组织域
  • 启用 ARC(Authenticated Received Chain)应对转发场景

3.4 检测思路

# 查看实际签名的头部覆盖范围(从收到的邮件原始头部分析)
# 关注 DKIM-Signature 里的 h= 是否包含 from、to、subject、date

# 检查自身域名的 DKIM 配置
dig +short TXT mail._domainkey.你的域名
# 确认 k=rsa 密钥长度不低于 2048(1024 位已不被推荐)

四、攻击链剖析:DMARC 策略的实际效力

4.1 p=none 等于没有防护

这是企业邮件安全里最常见也最致命的缺口。很多企业配了 DMARC 记录,看到 dig 查询有返回就以为完成了,实际上:

策略 收件方动作 对攻击者的影响
p=none 仅记录报告,不采取任何拦截 几乎无影响,伪造邮件照常投递
p=quarantine 放入垃圾箱 显著降低成功率,但用户翻垃圾箱仍可能中招
p=reject 直接拒收 有效阻断伪造投递

企业长期停留在 p=none 通常有几个原因:怕误伤业务邮件、缺乏 rua 报告分析能力、或者单纯是当初配完就没人管了。但从攻击者视角看,一个 p=none 的域名和完全没配 DMARC,实战效果差别很小。

4.2 对齐模式的选择空间

DMARC 的对齐要求分宽松和严格:

adkim=r / aspf=r(宽松,默认):只要求 DKIM 的 d= 域与 From 域属于同一组织域即可。
adkim=s / aspf=s(严格):要求精确匹配。

宽松模式下,如果攻击者能控制目标公司的任意子域(比如通过钓鱼获取了某个 SaaS 子域的配置权),就可能构造出对齐通过的伪造邮件,绕过 DMARC。这类"同组织域内横向利用"是实战中的高价值路径。

4.3 报告机制是防守方的眼睛

v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com;
  ruf=mailto:dmarc-forensic@example.com; fo=1;
  adkim=s; aspf=s; pct=100
标签 作用
rua 聚合报告 —— 所有以你域名发信但认证失败的来源 IP 汇总
ruf 取证报告 —— 获取伪造邮件的完整头部样本
fo=1 SPF 或 DKIM 任一失败就发报告
pct 策略应用比例,可灰度上线

不配置 rua 的 DMARC 等于放弃了唯一的可见性。有了 rua 报告,你能看到所有伪造来源;没有 rua,你连"自己的域名正在被伪造"都不知道。

五、运维实践:自建邮件服务器

以下内容用于理解邮件投递链路的实际配置。搞懂这些,才能准确判断自己的域名在哪个环节存在缺口。

5.1 环境准备

资源 要求
服务器 2 核 2G,Debian 12 / Ubuntu 22.04
出站 25 端口 必须开放(最关键)
域名 可自由配置 DNS 记录
反向解析 PTR 需服务商支持
cat /etc/os-release

# 邮件主机名务必用邮件子域,不要用根域名
hostnamectl set-hostname mail.example.com
echo "203.0.113.10 mail.example.com mail" >> /etc/hosts

为什么用 mail. 子域:根域名通常已有 A 记录指向 Web 服务;子域还能把邮件信誉与主站隔离,万一 IP 声誉受损不至于牵连主域名。

5.2 检查出站 25 端口

timeout 8 bash -c 'echo | nc -zv smtp.example.net 25'
timeout 8 bash -c 'echo | nc -zv smtp.example.org 25'
timeout 8 bash -c 'echo | nc -zv smtp.example.com 25'

正常输出应为 25 (smtp) open。国内云厂商通常需提工单解封且要求域名备案;境外 VPS 一般默认开放。

5.3 DNS 认证记录

类型 主机记录 记录值示例 作用
A mail 203.0.113.10 邮件服务器地址
MX @ mail.example.com(优先级 10) 邮件路由
TXT @ v=spf1 ip4:203.0.113.10 -all SPF
TXT _dmarc v=DMARC1; p=quarantine; DMARC
TXT mail._domainkey 公钥(见 5.5) DKIM

DMARC 建议分三步走,不要一上来就 reject:

阶段一(观察)  v=DMARC1; p=none; rua=mailto:dmarc@example.com
阶段二(隔离)  v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com
阶段三(拒收)  v=DMARC1; p=reject; rua=mailto:dmarc@example.com; pct=100

每个阶段观察 1-2 周 rua 报告,确认所有合法发信源都被覆盖后再收紧。

5.4 部署 Postfix 与 OpenDKIM

apt update
echo 'postfix postfix/main_mailer_type select Internet Site' | debconf-set-selections
echo 'postfix postfix/mailname string mail.example.com' | debconf-set-selections
apt install -y postfix opendkim opendkim-tools mailutils

postconf -e \
  'myhostname = mail.example.com' \
  'mydomain = example.com' \
  'myorigin = $mydomain' \
  'mydestination = localhost, localhost.localdomain' \
  'inet_interfaces = all' \
  'smtp_tls_security_level = may' \
  'smtpd_tls_security_level = may' \
  'smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination' \
  'smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination' \
  'milter_protocol = 6' \
  'milter_default_action = accept' \
  'smtpd_milters = inet:localhost:8891' \
  'non_smtpd_milters = inet:localhost:8891'

echo 'example.com' > /etc/mailname
postfix check && postfix reload

开放中继是致命风险:permit_mynetworks 让本机/内网免认证投递,很方便,但 25 端口绝不能对公网开放中继,否则服务器会变成垃圾邮件跳板,几小时内被各大 RBL 拉黑。务必确认 smtpd_relay_restrictions 含 reject_unauth_destination。

5.5 关闭 smtpd chroot(关键坑)

Debian 默认让 smtpd 跑在 chroot 沙箱,导致读不到密钥和配置,表现为 DKIM 签不上名。

编辑 /etc/postfix/master.cf,把 smtp 和 submission 行的第 5 列 y 改成 n:

sed -i 's|^smtp      inet  n       -       y|smtp      inet  n       -       n|' /etc/postfix/master.cf
sed -i 's|^submission inet n       -       y|submission inet n       -       n|' /etc/postfix/master.cf
postfix reload

grep -E '^smtp |^submission' /etc/postfix/master.cf

5.6 OpenDKIM 配置(含防头部注入)

mkdir -p /etc/opendkim/keys/example.com
cd /etc/opendkim/keys/example.com
opendkim-genkey -s mail -d example.com -b 2048
chown -R opendkim:opendkim /etc/opendkim

写入 /etc/opendkim.conf:

Syslog                  yes
SyslogSuccess           yes
Canonicalization        relaxed/simple
OversignHeaders         From
UserID                  opendkim
UMask                   007
Socket                  inet:8891@localhost
PidFile                 /run/opendkim/opendkim.pid
TrustAnchorFile         /usr/share/dns/root.key

Mode                    sv
KeyFile                 /etc/opendkim/keys/example.com/mail.private
Selector                mail
Domain                  example.com

OversignHeaders From 对应第三章讲的头部注入问题 —— 它让 From 头被二次签名,攻击者无法再追加一个伪造的 From。这行配置必须加。

systemctl enable --now opendkim
postfix reload
systemctl status opendkim | head -3

把公钥发布到 DNS:

cat /etc/opendkim/keys/example.com/mail.txt
# 把 p= 后面整段公钥去掉引号与换行,填入 mail._domainkey 的 TXT 记录

5.7 投递验证

echo "DKIM 测试 $(date)" | mail -s "DKIM test" your-mailbox@example.net

# 等 5-10 秒查看投递状态
journalctl --since '1 minute ago' | grep 'postfix/smtp' | grep status
# 期望: status=sent (250 Mail OK queued)

# 确认 DKIM 签名生效
journalctl --since '1 minute ago' | grep -i dkim
# 期望: DKIM-Signature field added (s=mail, d=example.com)

收件端打开「显示原始邮件」,逐项确认:

  • Authentication-Results: ... spf=pass
  • Authentication-Results: ... dkim=pass
  • Authentication-Results: ... dmarc=pass

三项全 pass 但依然进垃圾箱,通常是 IP 信誉问题(新 IP 需预热)或内容特征触发过滤。

5.8 PTR 反向解析

dig +short -x 203.0.113.10     # 应返回 mail.example.com

PTR 是很多收件方的隐式门槛:反查不到主机名会被直接降权。配置位置在云厂商控制台(如"弹性网卡 → 反向 DNS"),而非域名 DNS 面板。

六、授权演练框架:邮件安全意识测试

本章面向已取得书面授权的企业内部安全意识演练。目的是度量员工风险,不是获取凭证。演练全程应遵循最小影响原则。

6.1 授权前置条件(缺一不可)

项 要求
书面授权 由安全部门/管理层签署,明确演练范围、时间窗、目标人群
演练域名 使用专用演练域名,与生产域名隔离
数据合规 明确不收集真实密码、不落库敏感信息(只统计"是否点击")
应急预案 若员工误提交凭证,立即通知并协助改密
结果使用 只用于安全建设改进,不用于绩效考评

6.2 演练设计的核心指标

演练的价值在于量化,所以要提前定义口径:

指标 定义 说明
送达率 成功投递数 / 发送总数 反映邮件基础设施配置质量
进箱率 进收件箱数 / 送达数 反映域名信誉与认证配置
点击率 去重点击人数 / 送达数 核心指标,注意用"人数"不是"次数"
上/报率 主动上报人数 / 送达数 反映安全意识培训成效
响应时长 从发出到首次上报的时间 反映检测能力

统计点击率务必按去重人数计算。同一人多次点击只计一次,否则指标会超过 100%,失去意义。

6.3 演练执行要点

  • 时间窗选择:避开业务高峰与财报期,通常选工作日下午
  • 目标人群分层:按部门/岗位分批,便于定位高风险群体
  • 内容不要过度诱导:以"可识别性"为测试目标,不设计成员工无法分辨的完美伪装 —— 目的是发现培训缺口,不是"打败员工"
  • 落地页只提示不收集:点击后跳转到安全意识提示页即可,绝不收集任何凭证
  • 全程留痕:发送记录、时间戳、上报记录,作为交付物附件

6.4 结果记录模板

部门 送达 进箱 点击人数 点击率 上报人数 上报率
技术部 - - - - - -
市场部 - - - - - -
财务部 - - - - - -
人力资源部 - - - - - -

财务、人力资源、高管通常是钓鱼攻击的高价值目标,建议单独统计,作为培训重点。

6.5 演练后的闭环

数据统计
  → 定位高风险部门与人群
  → 针对性安全意识培训(不点名批评,讲共性问题)
  → 完善技术防护(DMARC 收紧、邮件网关规则、异常登录告警)
  → 3-6 个月后复测,对比改善幅度

演练必须形成闭环。只发不练、练完不培训不改进,等于白做还可能引发员工抵触。

七、防御加固与运维命令

7.1 服务器层面

# SSH 密钥登录,禁用密码
sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart sshd

# 防爆破
apt install -y fail2ban && systemctl enable --now fail2ban

# 防火墙:只放必要端口
apt install -y ufw
ufw default deny incoming
ufw allow 22/tcp && ufw allow 25/tcp && ufw allow 80/tcp && ufw allow 443/tcp
ufw enable

7.2 邮件层面

# 确认不存在开放中继
postconf -n | grep -E 'smtpd_relay_restrictions|smtpd_recipient_restrictions'

# 从外部测试(应返回 554 Relay access denied)
telnet 你的IP 25
# > MAIL FROM:<test@example.com>
# > RCPT TO:<someone@gmail.com>

# 速率限制,防凭证泄漏后被批量发信
postconf -e 'smtpd_client_message_rate_limit = 100'
postconf -e 'anvil_rate_time_unit = 60s'
postfix reload

其他建议:

  • 限制 mynetworks 只含 127.0.0.0/8,不要写 0.0.0.0/0
  • 启用 MTA-STS / TLS-RPT,强制对端使用 TLS
  • 定期检查 RBL 黑名单状态

7.3 员工侧识别要点

  • 显示名可以随意伪造,务必展开看真实发件地址
  • 子域名要警惕:mail.公司域名 与 公司域名 可能是两回事
  • 链接悬停看目标域名,警惕同形字符(rn 冒充 m、0 冒充 o)
  • 制造紧迫感的措辞是高危信号:"立即处理""账号即将停用""限时"
  • 异常附件类型:.htm、.html、.iso、.lnk
  • 邮件进垃圾箱本身就是认证未通过的提示,不要手动"标记为非垃圾邮件"后照常点击

7.4 常用运维命令

systemctl status postfix opendkim      # 服务状态
journalctl -u postfix -f               # 实时投递日志
postqueue -p                           # 邮件队列
postsuper -d ALL                       # 清空队列(谨慎)
certbot renew --dry-run                # 证书续期演练

# 域名认证体检
dig +short TXT 你的域名
dig +short TXT _dmarc.你的域名
dig +short -x 你的服务器IP

八、踩坑速查表

现象 原因 解决
25 端口测试超时 云厂商默认封禁 提工单解封或更换服务商
邮件进垃圾箱 SPF/DKIM/DMARC 任一失败,或 IP 无信誉 逐项 dig 验证,用 mail-tester 评分
DKIM 未签名 smtpd chroot 读不到密钥 master.cf 第 5 列改 n
SASL 认证失败 Debian chroot 隔离 同上,或改用 permit_mynetworks
myorigin 无效 /etc/mailname 优先级更高 同步写入 mailname
中文主题显示为空 未做 RFC2047 编码 主题用 =?UTF-8?B?xxx?=
被 RBL 拉黑 开放中继或发信量异常 检查 relay 限制,申请移除后预热 IP
PTR 反查失败 未在服务商侧配置 控制台申请反向解析
SPF permerror DNS 查询超过 10 次 收窄 include 链,减少嵌套
DMARC 配了但没效果 策略停在 p=none 按观察→隔离→拒收三阶段收紧

小结

邮件认证体系的每一环都有细节:SPF 决定谁能发,DKIM 保证内容完整,DMARC 决定失败后的动作,PTR 和 IP 信誉决定第一印象。

从攻击者视角看,这条链路的突破口集中在三处:SPF 的子域名缺口与 include 范围过宽、DKIM 的签名覆盖范围不足(头部注入)、DMARC 长期停在 p=none。多数企业的邮件安全风险并不来自高深技术,而来自这些配置细节的长期疏忽。

防守的正确姿势是:先把自己的域名体检一遍(子域名是否都配了 SPF、DKIM 是否 oversign、DMARC 是否还在 none),再通过授权演练量化员工风险,最后形成"检测→培训→改进→复测"的闭环。

本文技术内容仅用于自有系统运维与书面授权范围内的安全评估。未经授权的邮件伪造、诱饵系统搭建与凭证收集均属违法行为,请勿尝试。