【失败尝试】自研SMTP服务器前置中间件 密级: 【C-1】 | 时间:2024-06-27 | 目录:测试笔记 | 编辑本文 上一版本 | 版本差异 | ## 前言 [上一篇](https://wiki.rce.ink/view/?view_id=179&t=1719249438767 "上一篇")中我留了一个Todo。 使用内网穿透之后,会导致所有邮件的发件人IP异常。这是由于内网穿透会代理转发数据,邮件服务器获取到了内网IP就是进行内网穿透的客户端的IP。所以开启了SPF校验会导致所有邮件都不可达,都会被群晖邮服退信,只能够关闭不校验SPF。 而发信直接使用的内网25端口,不会再次经过外网服务器,会导致发送邮件的IP(家庭宽带、内部网络不出网的公网IP)与SPF标记的IP不一致,让邮件容易进入垃圾箱。即时是添加了SPF记录,后续需要一直调整SPF中的IP地址,并且大多数邮箱服务器会通过发件IP是否与域名存在DNS关联来进行邮件打分(可见上一篇文章中的mailer-test扣分项)。 发信的缓解措施,只有使用第三方smtp服务器进行发件,这样虽然在第三方邮件服务器有发件记录,但至少自己这边也有备份。当然,还要去配置这个发件邮箱进行自动转发(防止收件人直接进行回复),或者直接在邮件声明回复地址(但是会让收件人比较麻烦)。虽然麻烦一些,但没有更好的办法了。 剩下一个收信的问题,如何解决邮件伪造亟待解决。关闭了SPF,那么邮箱伪造的邮件都会随便进入邮箱。这是我不能够容忍的。 ## 自研SMTP服务器前置中间件 原理:发信邮服在查找域名邮局服务器的时候一定会建立25端口连接,那么实际上我们只要接管邮件服务器的25端口流量,再透传给内网的邮件服务器端口就行了。 这一步在go语言使用io.copy即可,发送一封QQ邮件,确实获取到了一些数据流量: ![](https://img.meituan.net/imgupload/3b96783944ebbadb1e3b6e4df124031b716723.png) 图中的流量我进行了hex编码,解码后伪造邮箱的发送可以拿到整个明文流量: ![](https://img.meituan.net/imgupload/6b2d2d5c8e482ca36e0bcf7308ff4ad3383937.png) 对流量进行解析,如下: 1、客户端(其他邮箱服务器)首先发送 EHLO 命令来标识自己并请求服务器列出其支持的SMTP扩展。服务器响应了一系列的 250 状态码,列出了它支持的扩展 2、客户端发送明文数据 3、客户端发送QUIT命令用于终止SMTP会话。客户端在完成邮件传输后,或者想要结束会话时,会发送这个命令。 4、250 2.0.0 Ok: queued as 4W7YXF61R0z9YXj 是由服务器发送的,确认邮件已经成功排队。 5、221 2.0.0 Bye 是由服务器发送的,确认接受客户端的 QUIT 命令,并准备关闭会话。 为什么单独拎出来这一点?因为我们发现使用QQ邮箱、outlook等大型邮件服务器,在第一步请求smtp拓展的时候发现群晖邮服出现了250-STARTTLS,将会使用TLS加密传输内容。 ![](https://img.meituan.net/imgupload/d4aa16382445916fa069023b80caec28101448.png) 这也就是为什么后面的数据进行hex解码会出现乱码了。我们原本的意图是,拿到明文传输的邮件内容,获取From里的关键信息,然后通过ns解析获取spf信任IP,再与发件人的IP进行对比。如果不一致,我的中间件将会识别邮件为垃圾邮件,同时我也能够做一些数据修改,增强拓展性。如何解决这个问题? #### 不应答StartTLS拓展,强制降级 能想到的是让中间件对拓展列表展示时,忽略掉```3235302d5354415254544c530d0a```。 测试之后发现,QQ邮箱和其他许多现代邮件服务提供商通常要求使用TLS(Transport Layer Security)加密来保护邮件传输的安全性。这意味着,当邮件服务器尝试与QQ邮箱服务器建立SMTP连接时,必须支持并启用STARTTLS扩展。即时能够关闭,我也不认为这是一个好的解决方案。 #### 解密TLS流量 TLS加密要经过多次握手,在buf的特征上是以160303...这样开头,里面的各个op记录了各种信息。例如服务器传递的证书、双方传递使用的随机数、选择的加密方法等。 所以需要一个好的tls握手包解析工具,在github和网上并没有找到好用的,于是我打算自己实现,也为了更好的理解tls的协议传输。 我的参考文章是: https://aandds.com/blog/network-tls.html https://datatracker.ietf.org/doc/html/rfc5246 https://megamorf.gitlab.io/2020/03/03/traffic-analysis-of-a-tls-session/ 根据文档编写解析规则,需要注意一些buffer叠加了多条message,需要根据规则分离出各个part,效果如下: ![](https://img.meituan.net/imgupload/102e1667372f4e219960c73d9cf9d9dd70815.png) TLS 第一次握手 - ClientHello的 时候会有一个random_data,打印出来 ![](https://img.meituan.net/imgupload/aa14bb43da5e2a057a211e2b6b56748f15569.png) TLS 第二次握手- ServerHello的时候也会有一个random_data,打印出来 ![](https://img.meituan.net/imgupload/42581ec7be397737e57ce91935428e9065841.png) 根据之前的server-hello获取选择的加密套件信息: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 1、密钥协商算法使用ECDHE; 2、签名算法使用RSA; 3、握手后的通信使用AES对称算法,密钥长度256位,分组模式是GCM; 4、摘要算法使用SHA384。 密钥协商算法使用ECDHE,不能无脑使用网上所说的一些加密pre_master_key,然后私钥解密就行。这也是为什么再exchange-client-key的环节,data并不是标准的RSA加密结果。 由于这个加密方法ECDHE,服务端的RSA私钥用于密钥交换,它不直接用于加密数据流。实际的数据流加密是使用客户端和服务器之间协商出的临时密钥对进行的,这些密钥对在握手过程中生成,并不依赖于服务器的RSA私钥。**也就是使用此套件即使获取RSA私钥依然无法解密通讯数据**。 所以,我们只能够修改client-hello环节,客户端显示的加密套件列表。(不在服务端选择加密套件的环节,也就是server-hello做修改,是担心选择的时候有前置逻辑不可控,导致出现问题,实际未测试) 以下是可选的加密算法 ``` TLS_RSA_WITH_AES_128_CBC_SHA:使用AES-128-CBC加密算法和SHA-1哈希算法,RSA密钥用于密钥交换。 TLS_RSA_WITH_AES_256_CBC_SHA: 使用AES-256-CBC加密算法和SHA-1哈希算法,RSA密钥用于密钥交换。 TLS_RSA_WITH_AES_128_GCM_SHA256: 使用AES-128-GCM加密算法和SHA-256哈希算法,RSA密钥用于密钥交换。 TLS_RSA_WITH_AES_256_GCM_SHA384: 使用AES-256-GCM加密算法和SHA-384哈希算法,RSA密钥用于密钥交换。 TLS_RSA_WITH_3DES_EDE_CBC_SHA: 使用3DES(Triple DES)加密算法和SHA-1哈希算法,RSA密钥用于密钥交换。 TLS_RSA_WITH_RC4_128_SHA: 使用RC4加密算法和SHA-1哈希算法,RSA密钥用于密钥交换。 ``` 这里测试,直接修改了client-hello时QQ服务器提供的加密套件列表,全部为009c(填充54组,与原始长度一致,这样不需要同步修改type前的length),但是发现中间件直接收到了明文流量? 由于群晖的默认TLS配置不支持低级的加密方式,直接导致QQ邮箱明文传输给我了.. 需要调整一下,配置文件给三个服务均设置为回溯兼容性,让服务器依然可以实现tls加密: ![](https://img.meituan.net/imgupload/8baf01e24090a5f3c0fc0a86e8bf0be042271.png) 发现没什么用,发现哪怕只是让套件列表没有c30c都不行,群晖只会选择ECC的TLS密钥协商方式吗? 如果是这样,那么我们就直接进行数据patch,获取长度位,然后根据长度将套件列表的字节全部填充为ff。发现无论是QQ邮箱、163还是outlook都会在三次连接后,发送明文邮件,似乎是一种降级策略。服务端支持STARTTLS,但是选择了一个异常的加密套件,最后用明文数据传输兜底(纯猜测) #### 可恶的gmail gmail使用了tls1.3协议进行密钥协商,相比tls1.2,它更安全,协商更快、效率更好。详见:https://zq99299.github.io/note-book2/http-protocol/05/04.html#https-%E5%BB%BA%E7%AB%8B%E8%BF%9E%E6%8E%A5 在我以为可以故技重施的时候,结果在我patch之后,选择了错误的加密套件后,数据链路中断后不再进行重试降级 ![](https://img.meituan.net/imgupload/a6640a2e941c9871b4b02ab445e9c92d173998.png) ![](https://img.meituan.net/imgupload/315eb4ef1cda5bc928ae58d3297213d3187852.png) 我脑子要炸了啊。因为解密不了的话,就拿不到来源邮箱。mailplus也没有提供钩子方法,拿不到任意信息。总不能够因为中间件的局限性,标记只接收xxxx的邮箱吧。这样也太愚蠢了。 想了很多办法,例如获取EHLO后的地址,但这也是一个可以被伪造的数据。再比如获取发送者的IP,是否可信。但是这需要庞大的库支持,并且对一些个人自建的邮局效果就很差了。 ## 结束 综上,邮件伪造无法解决是我无法容忍的,我还是放弃了使用群晖的mailplus+内网穿透的方案做个人邮箱服务器,期间学习到了很多网络原理与协议解析的知识,还是有点收获的。 评论列表 写评论 您的IP:18.219.75.171,临时用户名:8c4c7736评论已接入DepyWAF审计与流量系统,请勿频繁操作导致IP拉黑 提交评论 © 版权声明:非标注『转载』情况下本文为原创文章,版权归 Depy's docs 所有,转载请联系博主获得授权。