资讯动态

SMTP伪造发件原理与CentOS测试环境搭建及DMARC防护验证

发布时间:2026/10/9 14:14:17 来源:尧图企业网站定制
简介该资源面向网络安全初学者与邮件协议学习者聚焦SMTP协议原理及发件人伪造机制帮助理解邮件伪造的成因与防护思路。压缩包共36个文件约11.97MB以C源码为主体包含6个h头文件、4个cpp实现文件以及obj、pdb、ilk等编译中间产物另有res资源文件、manifest清单、vcproj与sln工程文件、exe可执行程序及ReadMe说明构成一套可直接编译运行的SMTP演示工程。已有1314人学习下载说明其在邮件安全入门方向具有一定参考价值。资源围绕SMTP连接建立、身份验证、MAIL FROM与RCPT TO命令提交、邮件传输等流程展开并涉及SPF、DKIM、DMARC等邮件验证技术的背景知识读者可借此理解发件人地址篡改的实现方式以及现代邮件服务器如何通过DNS记录与数字签名识别伪造邮件适合作为协议分析与安全防护学习的实践素材。1. 从一封“看起来像老板发的”邮件说起SMTP 伪造发件到底在伪造什么你收到过那种邮件吗发件人显示是公司财务总监标题写着“紧急付款”点开详情一看真实发信 IP 却来自某个云主机。这不是魔法而是 SMTP 协议设计之初就留下的口子。SMTP 伪造发件本质是在邮件投递的握手阶段让MAIL FROM和RCPT TO这两个命令里的地址与邮件头里的From:字段不一致或者干脆把From:写成任意地址。很多刚接触邮件伪造的人以为要黑进对方邮箱其实不用——你只需要一个能直连 25 端口的 SMTP 服务器或者一台自己搭的 CentOS 内网 SMTP 服务器就能把“伪造发件”跑通。这篇文章面向的是做邮件安全测试、反垃圾策略验证、或者单纯想搞懂邮件协议边界的一线工程师。我会把 SMTP 伪造发件的原理、CentOS 上搭建测试用 SMTP 服务器的步骤、伪造发件人字段的具体命令、以及怎么防住这类伪造按能复现的顺序讲清楚。先记住一个反直觉结论伪造发件成功与否不取决于你的脚本多高级而取决于目标邮件网关查不查 SPF、DKIM 和 DMARC。2. SMTP 伪造发件的协议底子为什么MAIL FROM和From:可以不一样2.1 一封邮件在 SMTP 会话里到底经历了什么要理解伪造发件得先看一次标准 SMTP 投递的原始会话。用telnet或nc连上任意邮件服务器的 25 端口你会看到类似下面的交互。这里我以本地搭建的测试 SMTP 服务器为例域名用test.local实际替换成你自己的测试域名。# 连接测试 SMTP 服务器的 25 端口 nc test.local 25 # 服务器返回 220 欢迎信息后依次输入 EHLO sender.test.local MAIL FROM:ceotest.local RCPT TO:victimexample.com DATA From: CEO ceotest.local To: victimexample.com Subject: Urgent payment Please transfer funds. . QUIT这段会话里MAIL FROM:ceotest.local是信封发件人也叫 Return-Path它决定退信退到哪里。而DATA段里的From: CEO ceotest.local是邮件头显示的发件人收件人客户端看到的就是这个。SMTP 协议本身不强制校验这两者必须一致也不强制MAIL FROM必须是你有权使用的地址。这就是伪造发件的根子。很多邮件网关只检查MAIL FROM的 SPF 记录却把From:头原样放行于是攻击者可以把From:写成任意地址而MAIL FROM用一个自己控制的合法域名来过 SPF。这种“信封与头部不一致”的玩法在邮件安全测试里叫“显示名伪造”或“头部伪造”。2.2 SPF、DKIM、DMARC 各自管哪一段不把这三个记录的分工搞清楚后面搭环境就是瞎调。SPF 是一条 DNS TXT 记录声明哪些 IP 有权用某个域名发信。它校验的是MAIL FROM里的域名不是From:头。DKIM 是给邮件头加数字签名收件方通过 DNS 里的公钥验证签名确认邮件内容没被篡改、且确实由域名持有者签发。DMARC 则把 SPF 和 DKIM 的结果与From:头里的域名对齐要求两者至少有一个通过且域名一致否则按策略处理。伪造发件能不能成功就看目标域有没有配 DMARC 的preject或pquarantine。如果目标域只配了 SPF 没配 DMARC你把From:写成该域MAIL FROM用自己域名SPF 检查的是你的域名照样通过而From:显示的是目标域——这就是最常见的伪造发件路径。2.3 为什么 CentOS 上搭内网 SMTP 服务器是常见测试起点做邮件伪造测试你不可能拿生产邮件服务器去发。常见做法是在内网用 CentOS 搭一台只负责投递的 SMTP 服务器关闭认证、允许任意发件人专门用来观察目标网关的反应。CentOS 上最省事的组合是 Postfix 加cyrus-sasl可选但测试伪造时通常把mynetworks设成内网段并关掉smtpd_relay_restrictions里的拒绝规则。这样你的测试脚本可以直连这台服务器用任意MAIL FROM发信。注意这台服务器只放在隔离内网不要暴露到公网否则就是给别人当跳板。下面一章我会把 CentOS 搭建内网 SMTP 服务器的具体命令和配置逐条写出来。3. 在 CentOS 上搭一台只用于伪造测试的 SMTP 服务器3.1 安装 Postfix 并关掉不必要的认证CentOS 7 和 CentOS 8 的包管理命令不同下面以 CentOS 7 的yum为例CentOS 8 换成dnf即可。安装 Postfix 后先停掉默认的 sendmail如果存在再改主配置文件。# 安装 Postfix yum install -y postfix # 停掉可能存在的 sendmail systemctl stop sendmail systemctl disable sendmail # 备份主配置 cp /etc/postfix/main.cf /etc/postfix/main.cf.bak # 编辑 main.cf关键项如下 vi /etc/postfix/main.cf在main.cf里测试环境需要改这几项。myhostname写你的测试域名mydomain同域myorigin设为$mydomain。inet_interfaces设为all或指定内网 IP。mydestination保留默认即可。最关键的是mynetworks把它设成你的测试机所在内网段比如192.168.1.0/24这样内网机器发信不需要认证。smtpd_relay_restrictions和smtpd_recipient_restrictions在测试环境可以暂时设为permit_mynetworks, permit但务必确认这台机器只在内网可达。改完执行postfix check检查语法再systemctl restart postfix。# 检查配置语法 postfix check # 重启 Postfix systemctl restart postfix # 确认 25 端口在监听 ss -lntp | grep :25这里有个血泪经验很多人改完main.cf直接重启结果 Postfix 起不来日志里写fatal: parameter inet_interfaces: no local interface found for ...。原因是inet_interfaces写了一个本机没有的 IP。先用ip addr确认本机 IP再填进去。另外 CentOS 默认可能开着 firewalld内网测试机要放行 25 端口否则你的脚本连不上。3.2 用swaks做第一轮伪造发件验证搭好服务器后别急着写 Python 脚本先用swaks这个命令行工具快速验证。swaks在 EPEL 源里安装后可以指定信封发件人和头部发件人正好用来演示伪造。# 安装 swaks yum install -y epel-release yum install -y swaks # 伪造发件人测试信封发件人用真实测试域头部 From 写成伪造地址 swaks --to victimexample.com \ --from ceotest.local \ --h-From: CEO ceofake-domain.com \ --server 192.168.1.10 \ --header Subject: Urgent payment \ --body Please transfer funds.这条命令里--from对应MAIL FROM--h-From:对应邮件头里的From:。--server指向你刚搭的 CentOS SMTP 服务器 IP。如果目标邮件网关没有 DMARC 对齐检查收件人看到的发件人就是ceofake-domain.com。参数说明--to是收件人--from是信封发件人--h-From:是显示发件人--header可以追加任意头。跑完看返回码250表示服务器接收550表示被拒。如果被拒先看 Postfix 日志/var/log/maillog确认是本地策略拒了还是目标网关拒了。3.3 用 Pythonsmtplib批量验证不同伪造组合swaks适合单发验证要批量测不同From:和MAIL FROM组合用 Python 的smtplib更顺手。下面这段脚本演示三种组合信封与头部一致、信封真实头部伪造、信封伪造头部真实。注意这里只连你内网的测试 SMTP 服务器不要指向任何外部真实邮件服务器。import smtplib from email.mime.text import MIMEText # 测试用 SMTP 服务器必须是内网隔离环境 SMTP_HOST 192.168.1.10 SMTP_PORT 25 def send_test(mail_from, header_from, to_addr): msg MIMEText(This is a spoofing test message.) msg[Subject] SMTP spoof test msg[From] header_from msg[To] to_addr try: # 不启用 TLS测试环境直连 25 端口 with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout10) as s: s.set_debuglevel(1) # 打印 SMTP 会话方便排查 s.sendmail(mail_from, [to_addr], msg.as_string()) print(f[OK] MAIL FROM{mail_from} From{header_from}) except Exception as e: print(f[FAIL] MAIL FROM{mail_from} From{header_from} - {e}) if __name__ __main__: to victimexample.com # 组合一信封与头部一致 send_test(ceotest.local, ceotest.local, to) # 组合二信封真实头部伪造 send_test(ceotest.local, ceofake-domain.com, to) # 组合三信封伪造头部真实 send_test(ceofake-domain.com, ceotest.local, to)这段脚本的关键在s.sendmail(mail_from, [to_addr], msg.as_string())。mail_from参数控制信封发件人msg[From]控制头部发件人。set_debuglevel(1)会把完整的 SMTP 会话打印出来你能看到服务器对每个命令的响应。如果组合二成功而组合三失败说明你的测试服务器对MAIL FROM做了限制但没管头部。参数上timeout10防止连接卡死生产环境不要用明文 25 端口发敏感内容。跑完这三组你就能摸清目标网关到底查哪一层。4. 伪造发件能过网关的三种典型路径与对应参数4.1 路径一目标域只有 SPF 没有 DMARC这是最常见的情况。目标域example.com配了 SPF 记录声明只有它自己的邮件服务器 IP 能发信。但没配 DMARC。你伪造From: ceoexample.comMAIL FROM用你自己控制的test.local。目标网关做 SPF 检查时查的是test.local的 SPF而test.local的 SPF 如果写all或者你根本没配 SPF检查结果可能是none或pass。由于没有 DMARC 做对齐网关不会因为From:和MAIL FROM域名不一致而拒信。收件人看到的就是ceoexample.com。要复现这条路径你的测试域需要有一条宽松的 SPF或者干脆不配 SPF让检查结果为none。很多网关对none的处理是放行。4.2 路径二利用显示名伪造绕过关键词过滤有些网关会检查From:里的邮箱地址是否与登录用户匹配但不太管显示名。你可以把From:写成CEO Zhang ceoexample.com显示名是中文或英文的“CEO”实际邮箱还是伪造的。更隐蔽的做法是From:写成ceoexample.com attackertest.local很多邮件客户端只显示显示名用户看到的是ceoexample.com实际地址是attackertest.local。这种伪造在swaks里用--h-From:直接写就行。参数上显示名用引号包起来尖括号里写真实地址。测试时注意部分客户端会把显示名和地址都显示出来所以这种路径对普通用户有效对懂行的管理员无效。4.3 路径三同域伪造——最容易被忽视的内网风险如果攻击者已经进入内网或者你的 SMTP 服务器mynetworks配得太宽内网任何机器都能用任意MAIL FROM发信。这时候伪造From: ceoyourcompany.com和MAIL FROM: ceoyourcompany.com完全一致SPF 检查因为源 IP 在内网而被视为合法。这种同域伪造最难防因为从协议上看没有任何异常。CentOS 搭建内网 SMTP 服务器时mynetworks一定要精确到具体测试机 IP不要写0.0.0.0/0。我见过有人图省事写mynetworks 0.0.0.0/0结果这台测试机成了内网钓鱼邮件的发射台。正确做法是mynetworks 127.0.0.0/8, 192.168.1.50/32只允许本机和指定测试机。5. 避坑与排查伪造发件测试里最容易翻车的 5 个点5.1 现象swaks返回550 Relay access denied原因mynetworks没包含测试机 IP这是搭完 CentOS SMTP 服务器后第一个拦路虎。你从测试机连过去发信服务器返回550 Relay access denied。原因通常是mynetworks里没写测试机的 IP或者写了网段但测试机不在那个网段。解决方法是ip addr确认测试机 IP把它加进mynetworks或者临时把smtpd_relay_restrictions设为permit_mynetworks, permit。改完postfix reload不用重启。注意permit放最后是兜底测试环境可以生产环境绝对不行。5.2 现象邮件进了收件箱但From:显示为unknown原因头部编码或格式错误你用 Python 拼From:时如果显示名包含中文或特殊字符没有做 MIME 编码收件端可能显示乱码或unknown。解决方法是显示名用email.header.Header编码或者干脆用纯 ASCII 显示名测试。swaks的--h-From:如果带中文也要确认终端编码是 UTF-8。更隐蔽的情况是From:后面没加空格直接跟某些解析器会把它当成无效地址。正确格式是From: Display Name userdomain.com引号、空格、尖括号都不能少。5.3 现象目标网关直接拒信并返回DMARC fail原因目标域配了preject这是伪造发件测试里最直接的失败。你发出去的邮件被目标网关以DMARC fail拒掉返回码通常是550 5.7.1。原因是目标域的 DMARC 记录是vDMARC1; preject;要求From:域名必须通过 SPF 或 DKIM 对齐。你的伪造From:域名没有通过任何一项所以被拒。这不是你的配置问题而是目标域防护到位。遇到这种情况不要试图绕过而是记录下这个结果——它说明该目标域对伪造发件有基本防护。测试报告里应该写“目标域 DMARC 策略为 reject伪造发件被正确拦截”。5.4 现象Postfix 日志里NOQUEUE: reject: RCPT原因收件人域名被本地限制有时候你发信到victimexample.comPostfix 日志写NOQUEUE: reject: RCPT from unknown[...]: 554 5.7.1 victimexample.com: Relay access denied。这通常是因为mydestination或relay_domains没包含example.comPostfix 不知道该把邮件投给谁。测试环境如果只是观察 SMTP 会话不真正投递可以把relay_domains设为example.com或者用smtpd_recipient_restrictions放行。但要注意放行后 Postfix 会尝试投递如果 DNS 解析不到example.com的 MX邮件会卡在队列里。用mailq查看队列postsuper -d ALL清空测试队列。5.5 现象脚本跑通但收件人没收到原因邮件被中间网关静默丢弃最让人抓狂的情况是 SMTP 会话返回250 OK但收件人收件箱里什么都没有。这不是你的脚本问题而是中间某个网关比如目标域的边界网关在接收后做了内容过滤或信誉检查静默丢弃了邮件。排查方法是看目标域网关的日志如果你有权限或者换一个自己控制的收件邮箱测试。如果自己控制的邮箱能收到说明投递链路没问题是目标域的策略在起作用。这种情况下伪造发件测试的结论应该是“SMTP 层通过但内容层被拦截”而不是“伪造失败”。6. 从伪造验证到防护验证用 DMARC 报告反推伪造发件是否被拦6.1 给自己控制的测试域配一条 DMARC 记录做伪造发件测试最终目的是验证防护有没有生效。最直接的办法是给你自己控制的测试域配一条 DMARC 记录然后从外部伪造该域发信看 DMARC 报告里有没有记录。DMARC 报告会以 XML 格式发到你指定的邮箱里面包含发信 IP、SPF 结果、DKIM 结果、处置动作。配置方法是在 DNS 里加一条 TXT 记录主机记录_dmarc值为vDMARC1; pquarantine; ruamailto:dmarctest.local; pct100。pquarantine表示可疑邮件进隔离箱rua是报告接收地址pct100表示对所有邮件生效。配完后等 DNS 生效再用swaks从你的 CentOS SMTP 服务器伪造From: ceotest.local发信。如果 DMARC 生效这封信应该被隔离并且你会收到一份报告。6.2 解析 DMARC 报告里的关键字段DMARC 报告是 XML关键字段在record下面。source_ip是发信 IPcount是邮件数量policy_evaluated里的disposition是处置结果none表示放行quarantine表示隔离reject表示拒绝。auth_results里分别有 SPF 和 DKIM 的resultpass或fail。如果你伪造From: ceotest.localMAIL FROM用test.localSPF 可能pass但 DMARC 对齐检查的是From:域名和MAIL FROM域名是否一致。如果两者都是test.local对齐通过DMARC 可能pass。要触发fail你需要让From:是test.local而MAIL FROM是另一个域且那个域的 SPF 不包含你的发信 IP。这样 SPFfailDKIM 也没签DMARC 对齐失败disposition就会是quarantine或reject。6.3 用报告反推防护缺口一个我踩过的坑我最早做 DMARC 验证时配了pquarantine但一直收不到报告以为配置没生效。后来发现是rua地址写成了dmarctest.local而test.local这个域根本没有 MX 记录报告发不出来。改成外部可收信的邮箱后报告立刻到了。另一个坑是pct没写默认是100但有些解析器对缺失pct的处理不一致建议显式写上。还有DMARC 报告是聚合报告通常 24 小时发一次不是实时。测试时不要发完信就等报告第二天再看。如果你要快速验证可以先用pnone加rua观察报告里的disposition是不是none确认链路通了再改成quarantine。这个顺序能帮你省下不少排查时间。6.4 把伪造测试固化成一条可重复的验证流水线单次测试靠手工敲命令时间长了必乱。我现在的习惯是把整个流程写成一个 shell 脚本放在测试机上每次改完配置跑一遍。脚本做三件事用swaks发三封不同伪造组合的邮件用grep扫/var/log/maillog确认 Postfix 接收状态用mailq确认没有卡队列。下面是一个简化版。#!/bin/bash # spoof_test.sh - 伪造发件验证流水线 SMTP_HOST192.168.1.10 TOvictimexample.com echo 测试一信封真实头部伪造 swaks --to $TO --from ceotest.local \ --h-From: CEO ceofake-domain.com \ --server $SMTP_HOST --body test1 21 | tail -5 echo 测试二信封伪造头部真实 swaks --to $TO --from ceofake-domain.com \ --h-From: CEO ceotest.local \ --server $SMTP_HOST --body test2 21 | tail -5 echo 检查 Postfix 日志 grep spoof /var/log/maillog | tail -10 echo 检查邮件队列 mailq | tail -5这个脚本里tail -5只取关键输出避免刷屏。grep spoof依赖你在邮件主题或正文里带了spoof关键词方便过滤。mailq检查队列如果测试邮件卡住用postsuper -d ALL清空。跑完这个脚本你能在 30 秒内得到一轮完整结果。我一般会在改完main.cf或 DNS 记录后跑一次确认没有回归。这个习惯帮我省了很多“以为改好了其实没生效”的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑