周六昏昏欲睡的下午,顺手用 goproxy 写了个小工具。功能部分不到两个小时就搞定了,但是在处理 https proxy 部分,希望实现一个自定义feature时,调试了挺长时间。这大大打击了自诩可以手撕 https/tls 的自我信心😝。于是顺手看了一下这块的实现部分。
原理上,https proxy 的处理,都是以客户端 CONNECT
请求开始,后续的请求都是通过这次建立的连接进行 req-rsp
交互。一句话就能讲完,很简单,对吧?对也不对。正确的部分在于,原理的确就是这样的,但是如何使用这个连接,以及如何处理其中的安全问题让这一块有很多细节需要考虑。所谓魔鬼在细节中,这也是这块最有意思的地方。
以 goproxy 这块的实现为例,通过 CONNECT
建立连接以后,请求的的交互主要支持4种方式:
- ConnectAccept
- ConnectHijack
- ConnectHTTPMitm
- ConnectMitm
ConnectAccept
是最基本的方式,只负责在 tcp 层建立远端和客户端的连接,这个连接具体怎么用,由客户端自己与远端交互决定。各个平台、各个客户端都支持这种方式,基本上没有兼容性问题。
ConnectHijack
与后面的两种方式本质上同一种类型,都可以认为是 Hijack
类型。这里涉及一个很重要的概念 Hijack
, 这个在 golang 标准库中有非常准确详细的解释,搬运如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Hijack lets the caller take over the connection. // After a call to Hijack the HTTP server library // will not do anything else with the connection. // // It becomes the caller's responsibility to manage // and close the connection. // // The returned net.Conn may have read or write deadlines // already set, depending on the configuration of the // Server. It is the caller's responsibility to set // or clear those deadlines as needed. // // The returned bufio.Reader may contain unprocessed buffered // data from the client. // // After a call to Hijack, the original Request.Body must not // be used. The original Request's Context remains valid and // is not canceled until the Request's ServeHTTP method // returns. |
也就是相较于 ConnectAccept
, proxy 不再是中间的小透明,而是可以接管连接,在中间实现一些自定义有意思的功能,这也是 MITM
的基石。所不同的是:
ConnectHijack
要求在业务自己在应用层实现这块逻辑,优点就是一切尽在掌握,可以实现很多有意思的功能。ConnectHTTPMitm
是一种 https 降级为 http 的一种实现。也就是 https 通过这个 proxy 后,都被降级为 http 请求与远端交互。优点是可以 offload TLS 这层的加解密和签名开销,但是完全没有安全性。因此,这种一种古老的方式已经不被绝大部分 client 支持了。ConnectMitm
则是一种经典实现,把客户端的 https 请求在中间做中转,然后还是以 https 的方式发送到远端。这是被client广泛支持的方式,因此兼容性较好。
需要说明的是:
Hijack
类型都依赖CA证书,这也是为什么你手机、电脑设备里面的CA根证书很重要,不要随便信任和安装来路不明证书的原因。- goproxy 在
ConnectHTTPMitm
和ConnectMitm
的实现中,都会再次调用filterRequest
执行 OnRequest 的 handler, 因此要在你的 request handler 中识别和处理这种经过转换的请求,否则会出现 loop 以及请求失败的情况。
从原理到实现,基本就拆解完了。其实也没有什么高深的部分,正如很多东西,不过是温故而知新,进一寸有一寸的欢喜。