# 内容摘要

  • spf 协议简介
  • nslookup 检索域名对应的 spf 记录
  • python 快速检索 spf 记录

# 1. spf 协议简介

  • SPF 全称 Sender Policy Framework,发件人策略机制

    是以 IP 地址认证电子邮件发件人身份的技术,域名所有者通过在 DNS 中发布 SPF 记录来授权合法使用该域名发送邮件的 IP 地址

    例子如下:

    从这个邮件头的数据中,我们可以看到 From 字段显示发件人是来自 163 的用户,但是在 smtp协议 中,这部分可以由用户自行修改。也就意味着,这个发件人地址完全可以是伪造的。

    邮件的发送过程可以由下图简略所示,图片来源:https://www.zacharyjia.me/index.php/archives/13/

    Received 字段是由邮件服务器在邮件转发过程中自动添加在邮件头部的,我们可以从中提取出发件服务器的原始 ip 123.126.97.1 ,通过 163 的 spf 记录来验证这份邮件是否是伪造的。

    163 的 spf 记录如下:

    1
    2
    3
    163.com	text = "v=spf1 include:spf.163.com -all"
    spf.163.com text = "v=spf1 include:a.spf.163.com include:b.spf.163.com include:c.spf.163.com include:d.spf.163.com include:e.spf.163.com -all"
    a.spf.163.com text = "v=spf1 ip4:220.181.12.0/22 ip4:220.181.31.0/24 ip4:123.125.50.0/24 ip4:220.181.72.0/24 ip4:123.58.178.0/24 ip4:123.58.177.0/24 ip4:113.108.225.0/24 ip4:218.107.63.0/24 ip4:123.58.189.128/25 ip4:123.126.96.0/24 ip4:123.126.97.0/24 -all"

    通过上面的结果,我们可以看到 ip4:123.126.97.0/24 被授权使用 163.com 的域名,所以这封邮件是真实的。

  • 接下来,我们来简单学习一下 spf 的语法规则

    参考文档:https://service.mail.qq.com/cgi-bin/help?subtype=1&no=1001505&id=16

    SPF 记录由 SPF 版本和指定 IP 组成

    record = version terms *SP

    version = "v=spf1" 即指定 SPF 的版本为 spf1

    terms 由机制 mechanisms 和修改符 modifiers(可选)组成,mechanisms 用来描述哪些 IP 被允许使用该域名发送邮件

    Mechanisms 包含以下几种类型

    mechanism = (all / include / a / mx / ptr / ip4 /exists)

    (一) 机制 mechanisms

    每个 mechanism 有四种前缀(默认前缀为 "+")

    “+” Pass

    “-” Fail

    “~” SoftFail

    “?” Neutral

    在一条 SPF 记录中,从左到右依次对每个 mechanism 进行验证。对一个 mechanism 进行检测,有三种结果可能发生:IP 匹配成功,IP 匹配失败或者返回异常。如果 IP 匹配成功,处理结果返回该 mechanism 的前缀;如果 IP 匹配失败,继续下一个 mechanism;如果返回异常,则 mechanism 结束并返回该异常值;如果没有 mechanism 或者 modifier 匹配,则结果返回 “Neutral”。

    如果不存在 SPF 记录,则返回 “None”;如果在 DNS 解析过程中出现临时性错误,则返回 “TempError”;如果存在某些语法错误或者评估错误(如该域指向不为人知的机制),则返回 “PermError”。

    SPF 记录验证可能返回的结果如下:

    Result Explanation Intended action
    Pass SPF 记录验证该发信 IP 为合法的 接收邮件
    Fail SPF 记录验证该发信 IP 是不合法的 拒绝邮件
    SoftFail SPF 验证该发信 IP 不是合法的,但是不采取强制措施 接收邮件但作标识
    Neutral SPF 记录没有明确说明发信 IP 是否合法的信息 接收邮件
    None 域名没有设置 SPF 记录或者 SPF 记录验证没有结果 接收邮件
    PermError 发生永久性错误(如:SPF 记录格式错误) 没有规定
    TempError 发生临时性错误 接收或拒绝邮件

    1. “all” 机制

    “all” 表示所有 IP 都匹配。通常放在 SPF 记录末尾,表示处理剩下的所有情况。

    例如:

    “v=spf1 mx -all” 表示允许所有该域的 MX 邮件服务器发送邮件,禁止其他的。

    “v=spf1 -all” 表示该域不会发送任何邮件。

    “v=spf1 +all” 表示域名所有者认为 SPF 是没有用的或者并不关心(任何服务器都可使用该域名发送邮件)。

    2. “ipv4” 机制

    ip4:<ip4-address>

    ip4:<ip4-network>/<prefix-length>

    如果没有提供 prefix-length ,默认为 /32。

    3. “a” 机制

    格式为:

    a

    a/<prefix-length>

    a:<domain>

    a:<domain>/<prefix-length>

    所有 A 记录都会检测。如果客户端 IP 在这些记录中,则该机制结果匹配。

    若没有指定域,则使用当前域。

    A 记录必须与客户端 IP 地址完全匹配,除非提供前缀长度,在这种情况下,A 记录查询返回的 IP 地址将扩展到其相应的 CIDR 前缀,且客户端 IP 将在其子网中查询。

    例如:

    “v=spf1 a -all”

    表示当前域被使用。

    “v=spf1 a:example.com -all”

    等价于当前域为 example.com

    “v=spf1 a:mailers.example.com -all”

    表示指定 mailers.example.com 的主机 IP 可以外发邮件。

    “v=spf1 a/24 a:offsite.example.com/24 -all”

    如果 example.com 解析到 192.0.2.1,那么整个 C 类地址 192.0.2.0/24 将作为客户端 IP 地址外发邮件。同样,如果 offsite.example.com 返回多个 A 记录,每个 IP 地址将被扩展到 CIDR 子网。

    4. “include” 机制

    格式为:

    include:<domain>

    表示指定域查询匹配。若查询返回不匹配或者有错误,接着处理下一个机制。警告:如果指定域没有一个有效的 SPF 记录,结果将返回永久性错误。某些邮件接收者会根据 “PermError” 拒绝接收邮件。

    例如:在下面例子中,客户端 IP 为 1.2.3.4,当前域为 example.com.

    “v=spf1 include:example.com -all”

    若 example.com 没有 SPF 记录,结果为 “PermError”。

    假设 example.com 的 SPF 记录为 “v=spf1 a -all”,查询 example.com 的 A 记录,

    若与 1.2.3.4 匹配,返回 “Pass”;

    若不匹配,处理包含域的 “-all”,include 整个匹配失败;

    从这个例子的外部指令集可见,最终的结果仍为失败。

    因为信任关系,“include” 机制可能会被认为有越权行为。需要确保 “include” 机制不会给跨用户伪造的信息 SPF 验证返回 “Pass”。除非对指定的其他域技术机制到位,从而反正跨用户伪造,“include” 机制应该提供 “Neutral” 处理结果而非 “Pass”。即在 “include:” 前添加 “?” 前缀。例如:

    “v=spf1 ?include:example.com -all”

    如此看来,“include” 机制不宜选择。

    更多详细内容可以参考 https://service.mail.qq.com/cgi-bin/help?subtype=1&no=1001505&id=16

  • 根据上述语法,我们再分析 163 的 spf 记录

    1
    2
    3
    163.com	text = "v=spf1 include:spf.163.com -all"
    spf.163.com text = "v=spf1 include:a.spf.163.com include:b.spf.163.com include:c.spf.163.com include:d.spf.163.com include:e.spf.163.com -all"
    a.spf.163.com text = "v=spf1 ip4:220.181.12.0/22 ip4:220.181.31.0/24 ip4:123.125.50.0/24 ip4:220.181.72.0/24 ip4:123.58.178.0/24 ip4:123.58.177.0/24 ip4:113.108.225.0/24 ip4:218.107.63.0/24 ip4:123.58.189.128/25 ip4:123.126.96.0/24 ip4:123.126.97.0/24 -all"

    第一条记录,说明 163.com 的 spf 记录可以通过 spf.163.com 进行查询, -all 表明除了这个记录包含的 ip 地址,都会匹配失败

    第二条记录,说明 spf.163.com 的 spf 记录包含在 a.spf.163.comb.spf.163.comc.spf.163.comd.spf.163.come.spf.163.com 几个域名的记录之中

    第三条记录,说明了 a.spf.163.com 所包含的 ip 地址集合,其中 123.126.97.1ip4:123.126.97.0/24 之中,所以发件人确实为 163 的用户。

# 2. nslookup 检索域名的 spf 记录

  • 命令行

    1
    2
    3
    4
    5
    nslookup
    set type=TXT
    163.com
    spf.163.com
    a.spf.163.com

    实际运行结果如下:

# 3. python 快速查询 spf 记录

  • 第三方包 SPF2IP, netaddr

    下载

    1
    2
    pip install netaddr
    pip install SPF2IP
  • 样例程序

    1
    2
    from SPF2IP import SPF2IP
    spf_record = SPF2IP('163.com').IPArray('4')

    结果:

    快速判断一个 ip 是否在一个子网中

    1
    2
    3
    from netaddr import IPNetwork, IPAddress
    if IPAddress('123.126.97.1') in IPNetwork('123/126.97.0/24'):
    print('True')

    结果:

萬葉集
鸣神の 少しとよみて さし昙り 雨も降らんか 君を留めん
鸣神の 少しとよみて 降らずとも 我は止まらん 妹し留めば
万叶集
隐约雷鸣 阴霾天空 但盼风雨来 能留你在此
隐约雷鸣 阴霾天空 即使风无雨 我亦留此地
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

chaihj15 微信支付

微信支付

chaihj15 支付宝

支付宝