MITM 劫持的过程中,HTTP 协议并不是唯一选择。
实际在 MITM 使用过程中,BurpSuite 和 Yakit 提供的交互式劫持工具只能劫持 HTTP 代理的 TLS 流量;但是这样是不够的,有时候我们并不能确保 HTTP 代理一定生效,或者说特定的后端只支持 TCP 传输层代理(Socks5)代理,那么交互式劫持将失效,也没有办法获取到基于 Socks5 代理的 HTTP 通信。
因此我们做了一些简单的验证,去实现了基于 Socks5 协议的代理劫持,同时我们以一种更加简单高效的方式让 Socks5 的代理适配了传统的普通 MITM 交互式劫持中。
TLS 劫持的本质:证书信任链的崩坏
- 内容引用自笔者在他处的 PPT 中的节选内容
因为信任链是树形结构的,可以“推导”信任关系,那么被信任的 CA 签发的证书,也会被信任。
那么理所当然的,我们得出结论:从原理上来讲,TLS 的劫持和 HTTP 代理甚至任何代理都没有半毛钱关系;从技术上来讲,HTTP 代理在其中的作用仅仅是提供了一个连接介质。
MITM-TLS 劫持时序
回顾基于HTTP 代理的MITM 劫持
如果要实现一个正常的 HTTP 代理,我们需要处理两种情况:
- 通过 Proxy-Connection 等直接 HTTP 头控制的非 TLS 劫持
- 通过 CONNECT 控制的通用 HTTP 代理。
在第一种情况下,代理只需要去掉 Proxy 头,解析出对应的 Host 地址,发起明文 HTTP 链接即可。我们就不做过多解释;在第二种情况下,CONNECT 成功发起后,TCP 连接将被建立,中间人需要同时进行客户端的握手,同时尽可能还原信息去和服务端进行握手。实际上,从 TCP 连接建立开始就和 HTTP 代理没有任何关系了。实际上在 Socks5 代理中,从 TCP 连接开始建立之前和 HTTP 代理是不一样的,但是从 TCP 连接开始建立之后,如果我们要进行劫持,那么关于劫持的技术实现和现有 TLS 劫持技术是几乎一致的。
Socks5 可进行中间人攻击的代理实现
我们要实现这个技术,一般的 Socks5 服务器一般来说也是不好使的,所以我们需要花几分钟来学习一下一个简单好用的 Socks5 协议应该怎么实现。
Socks5 一般有三种模式:
-
TCP Forward: 最常见的模式,正向代理,转发客户端的所有连接
-
TCP Port Forward: 反向模式,映射远端一个端口,然后让客户端自行连接
-
UDP Forwarded:UDP 转发模式
一般的 Socks5 服务器和客户端都只支持第一种协议,因此我们以第一种协议为例,拆解一下通信过程,我们把通信过程分为:
-
握手
-
连接
Socks5 握手
- 代码仅供参考。
Socks5 转发
当我们实现握手之后,我们发现拿到了 TCP 连接,那么正常来说,我们此时就有了决策点:
- 如果我们正常无脑转发 TCP 到目标 TCP 连接中,那我们的 Socks5 就是一个 “正义” 的 Socks5 代理
- 当我们劫持过程中,尝试预读(Peek)TLS Handshake ClientHello 的时候,我们就可以知道这个连接到底是不是 TLS 握手,要不要伪装了。具体的代码如下:
Socks5 邪恶版本实现之后,我们就可以顺利劫持到 TLS 的原始内容了。
备注:SNI 仍然不受任何影响
SNI 在 TLS Handshake ClientHello 中作为 TLS Extension 信息,他的存储结构其实并不是一个 string,而是一个 []string,我们仔细回想一下,劫持后的 TLS 握手发生的时间肯定至少应该在上一个 SNI 的解析出来之后,如果没有解析出来,那么应该读取 Socks5 连接的时候的目标 Addr。
工程问题:如何使用现有的基础设施?
经过上面的 proof of concept,我们顺利实现了 Socks5 代理的 TLS 劫持,但是我们发现他劫持的实际上并不是真正的 HTTP 请求,而是更加通用的 TCP 请求,这下遇到了问题了:“MITM 的基础设施一般都是用来处理 HTTP 请求的,那么接下来难道要完全重写一遍基础设施吗?”,我们发现,让这个小技术更好的造福用户和他的技术实现还是有很大差距的,那么真的束手无策了吗?
多协议端口复用(Socks5 与HTTP 代理共存)
大家很多时候仍然记得我们在 Yakit 中的 “反连” 中实现了一个端口同时 Serve 多种不同的应用协议的功能;很自然地,我们知道 Socks5 的第一个字符 \x05 和 \x16(TLS) 并不冲突,因此 Socks5 其实也可和 HTTP 协议并存,因此 ClashX 等工具的本地代理你可以设置为 Socks5 代理,也可以设置为 HTTP 代理。
很自然的,我们为了减轻用户切换代理的负担,第一件事儿应该是让 Socks5 和 HTTP 共存。
-
通过我们的 TCP Connection Peek 技术很容易实现这个功能
-
Facades 已经提供了长时间健康运行的证明,这个技术并不会额外增加负担
因为我们很快就解决了这个小问题
以退为进:巧妙利用二级代理
我们使用 Socks5 劫持 TLS 本质上是劫持了 TCP(HTTP 的传输层),并不是劫持了 HTTP,按理说并不能兼容 HTTP 代理,但是如果我们主动放弃除 HTTP 之外的其他协议,那么是否可以服用基础设施呢?既然决定自我退化,那么实际上这个 “劫持” 问题可以降级为:
“如果我能给 Socks5 转发的 TCP 连接增加一个二级代理,这个二级代理是有劫持功能的,那么是不是说也算是成功劫持?”
结合多协议复用技术,我们实现了如下有趣的技术内容
-
我们预读了 HTTP 代理中的 Socks5 代理请求,手动进行了 Socks5 握手
-
如果握手成功,我们将会把 TCP 连接当成 Socks5 代理连接来使用,同时让 Socks5 服务器启动新连接的时候设置同端口的 HTTP 代理;
-
HTTP 协议被 HTTP 代理直接劫持!完美兼容 Socks5 代理协议
最终实现
细心的同学在 Yakit 的使用中发现:曾几何时 MITM 的提示词已经变成 http://127.0.0.1:8083 或 socks5://127.0.0.1:8083。
既然最近有对 MITM 的调整,那么也顺便来说一下 MITM 的其他变化:
国密GM-TLS 支持
虽然大家在 yak 核心引擎中还能看到 martian 的影子,但是需要提醒的是,这个 martian 已经不是大家认识的 google/martian 了,我们对他进行了大量 Bug 修复,上下文控制以及 Socks5 支持,认证代理支持的修改,甚至我们还在他的基础上支持了国密算法的劫持套件,以助力对国内一些特殊站点的测试。
同时,支持 GMTLS 我们还支持多种选项:
-
国密 TLS 优先:当这个选项开启后,进行 TLS 劫持后连接时将优先进行 GMTLS 连接
-
仅国密 TLS:当这个选项开启后,将不再支持普通 TLS 的劫持功能
当然,普通模式下,TLS 如果连接不成功,会自动进行 GMTLS 的备份连接,前提是需要开启 “国密劫持” 选项。
代理认证:
当然,我们现在 MITM 劫持是可以开启认证的,用户可以输入用户名和密码以限制自己的 TLS 不会被其他人连接。
当我们在同一端口上进行了 Socks5 和 HTTP 代理的时候,将会同时对 Socks5 和 HTTP 代理启用认证。大家尽可放心使用。
虽然我们实现它的技术难度并不高,但是这却是一个非常有趣的技术点,在大家配置和使用的过程中已经发挥了巨大的作用。如法炮制,大家可以用同样的技术和思路为 BurpSuite 实现一个 Socks5 代理。