avatar
CRUMBLEDWALL
Keep Curious
DataCon 2023 邮件赛道小记
Nov 22,2023

前几天参加了今年的 DataCon 邮件方向,跟组里同学狂肝了10天,最后成功混了个冠军,还不错hhh,期间也学了不少邮件伪造的技巧,稍微记录一下。

筛选钓鱼邮件

第一题是个数据集分类题,也算是 DataCon 常出的题型,提供的是一堆邮件 eml 文件,然后这题提供的数据集是只有垃圾邮件和钓鱼邮件样本的数据集,需要提供一个分类程序,在含有正常邮件、钓鱼邮件和垃圾邮件的测试集里把钓鱼邮件拎出来。所以分析一下感觉基本不太能跑机器学习,然后因为因为需要区分垃圾邮件和钓鱼邮件,Header 里的特征感觉没法用来做区分,用了可能疯狂误报,所以最后搓出来一个正文+附件的纯规则程序,跟同学疯狂加班优化了n版,之后效果居然意外的好,准确率90%+,果然能规则的还是规则好用hhh。

邮件投递

主要感觉还是第二题比较有趣,题目给了访问频率 Top 1W 域名,需要以这些域名为发件人,向目标邮箱里投递邮件,每个域名发10封以上的邮件就不再计分,然后需要投递到收件箱里,并且通过 SPF、DKIM 和 DMARC 校验各得一分。靶场在比赛后五天开放,每天开放两小时,根据邮件投递数量计分。

之前打 AliyunCTF 的时候就做过一道邮件伪造的题,当时拿到这道题第一反应这题也是考察邮件伪造,不过折腾了半天,到靶场开放的时候发现 checker 逻辑跟想象的不太一样,又开始研究重放之类的方法,最终再结合手摇自行车(x,成绩也还可以。

邮件伪造

既然研究了快10天的伪造和重放,还是来记录下各种协议的绕过姿势。

首先来聊聊邮件防止伪造的三个校验协议,分别是 SPF、DKIM 和 DMARC 协议,

SPF 协议

SPF协议即发件人策略框架(Sender Policy Framework),根据发信域名 DNS 的 SPF 记录配置给出的 IP,判断实际的发信 IP 是否合法,不合法则标记为垃圾邮件。

DKIM 协议

域名密钥识别邮件(DomainKeys Identified Mail),在发信域名的 DNS 上设置 DKIM 公钥,发信时通过私钥加密邮件相对应内容,生成 DKIM 签名及相关信息插入到邮件标头中;当邮件服务器收到了邮件时,通过 DNS 查询公钥,验证邮件 DKIM 签名的有效性,从而确认在邮件发送的过程中邮件是否篡改。

DMARC 协议

基于域的消息认证,报告和一致性(Domain-based Message Authentication, Reporting & Conformance),校验 SPF 记录和 DKIM 记录是否通过,并验证通过的域名与邮件报文中的FROM地址是否一致。

校验绕过

SMTP 代发

对于只需要绕过 SPF 校验和 DKIM 校验的情况,基本可以采取通用的手法来绕过,网上很多文章也都是基于这种思路。

首先可以考虑寻找 SMTP 服务并通过其来中转,网上有几个博客都是使用 smtp2go 来发邮件的,不过比赛的时候试了一下,这个平台已经开始严格校验 body 里的 FROM 头跟 mailfrom 是否对应了,所以当时比赛的时候就找了一圈,发现163邮箱和88完美邮箱居然都没做这个校验,那就可以通过这两个邮箱来发代发邮件了,简单写个调用 SMTP 库的脚本就可以发送(截止到写这篇文章的时候这种操作还可以使用)。

from email.message import EmailMessage
import smtplib

sender = "***@88.com"
recipient = "victim@aaa.com"


email = EmailMessage()
email["From"] = "管理员<admin@google.com>"
email["To"] = recipient
email["Subject"] = "Test Email"
email.add_header('Content-Type','text/html')
email.set_payload('2222')

smtp = smtplib.SMTP("smtp.88.com", port=465)

smtp.login("***@88.com", "***")
smtp.sendmail(sender, recipient, email.as_string())
smtp.quit()

或者也可以用 swaks 指定 SMTP 服务来发送

swaks --to victim@aaa.com --from ***@88.com --header-X-Mailer SMTP  --body '您的所有Apple设备已被禁用' --server smtp.88.com -p 25 -au ***@88.com -ap ***  --header "Subject:账户通知"  --h-From: '管理员<admin@apple.cn>'

这种方式发出的邮件如下图所示,会多一个代发提示,但是可以发进Coremail的收件箱里。不过QQ邮箱之类的现在已经 ban 掉这种只通过 DKIM 和 SPF 校验的代发邮件了,网易自己倒是能接收。

除了上述方式之外,也可以 Espoofer 来实现跟上面这种攻击方式差不多的效果,配一个给自己的域名配好 DKIM 和 SPF 对应的 TXT 解析记录,然后稍微改一下 testcase.py 里的配置,就可以使用自己的域名来伪装成 MTA 来直接跟目标服务器交互,优点是批量发送不用担心被 SMTP 提供商 gank,不过缺点是用自己的域名信誉肯定会差很多。

因为比赛到第六天才开放靶场,第二天搓好脚本之后骚扰了好几次主办方(感谢出题人没有嫌我烦),问这种发进收件箱的邮件算不算分,当时收到的回复是只看 From 头,结果到靶场开放后发现并不能得分😥,checker 应该是自己又做了 DMARC 的校验,过不了校验的,DKIM 和 SPF 的分也得不到。没办法只能找其他的思路了。

SPF 松散配置

然后有天突发奇想,想看看能不能撞大运找到 top 1w 域名里 SPF 记录配的比较松的,可以随便拿哪个 ip 发都通过 SPF 校验,然后写了个脚本批量查 TXT 记录里的 SPF 配置,没想到还真找到3个,甚至还有 ubuntu 在里面,总之也是能混一下这三个域名的分。

DKIM 重放

一开始几天怎么想都觉得 DKIM 是没法绕的,因为基本上绝大多数邮件肯定都会对 TO Header 签名,但是有天靶场开放的时候随手用 QQ 邮箱给目标邮箱发邮件试了一下突然发现,抄送甚至是密送的邮件居然都可以得分,这也就是说 checker 并没有校验 TO Header 必须是目标邮箱。其实仔细想想这也是合理的,毕竟是对收件箱进行 DOS,所以发给谁确实不是那么重要。

那么基于这个事实,我们就可以进行 DKIM 重放来进行批量的邮件发送。实操起来基本只需要把发向主办方测试邮箱的邮件原文复制一下保存起来,然后简单改一下 Espoofer 的代码,用 Server 模式批量进行发包就可以了。

绕过总结

最后找到的绕过方法就上面这几种了,像 USS20 论文里提到的一些 DMARC 绕过方式很多就是基于字符串解析和字符串截断的,在成熟的邮件服务器上已经不太容易遇到了,所以现在我们进行邮件伪造时想绕过 DMARC 校验其实还是比较难的,这些方法也不整理了。

对于这道题来说,我们最后对得分期望比较高的域名是直接手摇的,当时也算是队里全员出动hhh。然后风控了的,或者不太能发10封的,就直接来重放,得满10封的 DKIM 分,毕竟这种重放得不了 SPF 的绕过分。然后对于 top 1k-1w 这种得分期望低的,就把找到的样本全部拿来重放了。

碎碎念

之前考研选组的时候看到组里学长学姐拿过 Datacon 冠军之类的,还有点小憧憬。毕竟只打过 CTF,对数据赛道啥的还是比较好奇的状态。不过随着去年和今年先后混了个季军和冠军,也算是好好体验了一把,现在也是有了丝丝过来人的感觉hhh。后面一段时间就投身开题已经之前写的开源项目上了,继续好好干吧。

Copyright @ 2018-2024
Crumbledwall