邮件安全攻防:从 SPF/DKIM/DMARC 绕过原理到授权演练框架
- 安全运维
- 21分钟前
- 17热度
- 0评论
前言
邮件安全里有一类问题特别典型:你明明配置了 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=passAuthentication-Results: ... dkim=passAuthentication-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),再通过授权演练量化员工风险,最后形成"检测→培训→改进→复测"的闭环。
本文技术内容仅用于自有系统运维与书面授权范围内的安全评估。未经授权的邮件伪造、诱饵系统搭建与凭证收集均属违法行为,请勿尝试。
