加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

HTTP被动扫描代理的那些事

发布时间:2019-09-11 02:54:56 所属栏目:教程 来源:koalrx
导读:HTTP 代理这个名词对于安全从业人员应该都是熟知的,我们常用的抓包工具 burp 就是通过配置 HTTP 代理来实现请求的截获修改等。然而国内对这一功能的原理类文章很少,有的甚至有错误。笔者在做 xray 被动代理时研究了一下这部分内容,并整理成了这篇文章,

这里我们需要重点关注下 TLS 握手过程。在 TLS 握手过程中会进行证书校验,如果客户端访问的是 baidu.com,server 需要有 baidu.com 这个域的公钥和私钥才能完成握手,可是我们手里哪能有 baidu.com的证书(私钥),那个在文件在 baidu 的服务器上呢!

解决办法就是文章最开始说到的信任根证书。信任根证书后,我们可以在 TLS 握手之前直接签发一个对应域的证书来进行 TLS 握手,这就是包括 burp 在内的所有需要截获 HTTPS 数据包的软件都需要信任一个根证书的原因!有了被系统信任的根证书,我们就可以签出任意的被客户系统信任的具体域的证书,然后就可以剥开 TLS 拿到被动扫描需要的请求了。这里还有一个小问题是签发的证书的域该使用哪个,简单起见我们可以直接使用 CONNECT 过程中的地址,更科学的方法我们后面说。签完证书就可以完成 TLS 握手,然后就又和第一节的情况类似了。

有个点需要提一下,如果不需要进行中间人获取客户端请求,是不需要信任证书的,因为这种情况下的是真正的隧道,像是客户端与服务器的直接通信,代理服务器仅仅在做二进制的数据转发。

至此,被动代理的核心实现已经完成了,接下来是一些琐碎的细节,这些细节同样值得注意。

代理的认证

一个公网的代理如果没有加认证是比较危险的,因为代理本身就相当于开放了某个网络的使用权限,而且由于隧道模式的存在,代理的支持的协议理论上拓宽到了任何基于 TCP 的协议,如果可以和传统的 redis 未授权,SSRF DNS rebinding 等结合一下就是一个简单的 CTF 题。所以给代理加上鉴权是很有必要的。

代理的认证和正常的 HTTP Basic Auth 很像,只是相关头加了一个 Proxy- 的前缀,可以参考 《HTTP 权威指南》中的一个图学习一下:

HTTP被动扫描代理的那些事

点对点的修正

根据 RFC,HTTP 中的下列头被称为单跳头(Hop-By-Hop header),这些 Header 应该只作用于单个 TCP 连接的两端,HTTP 代理在请求中如果遇到了,应当删掉这些头。

  1. "Proxy-Authenticate", 
  2. "Proxy-Authorization", 
  3. "Connection", 
  4. "Keep-Alive", 
  5. "Proxy-Connection",  
  6. "Te", 
  7. "Trailer", 
  8. "Transfer-Encoding", 
  9. "Upgrade", 

至于这些头要删掉的原因,这里按我的理解简单说下。前两个是和认证相关的,每个代理的认证是独立的,所以认证成功应该删掉当前代理的认证信息。

中间的三个是用于控制连接状态的,TCP 连接是端到端的,连接状态的维护也应该是针对两端的,即客户端与代理服务器, 代理服务器与目的服务器应该是分别维护各自状态的。Proxy-Connection 类似 Connection,是用来指定客户端和代理之间的连接是不是 KeepAlive 的,代理实现时应该兼顾这个要求。对于连接的状态管理,我认为比较科学的方式是分拆而后串联。分拆是说 client->proxy 和 proxy -> server 这两个过程分开处理, client->proxy 的过程每次开启新的 TCP 连接,不做连接复用;而 proxy->server 的过程本质上就是一个普通的 http 请求,所以可以套一个连接池,借助连接池可以复用 TCP 连接。两部分的连接都拨通后,可以将其串联起来,最终效果上就是在遵循 Proxy-Connection 的前提下连接的状态最终与代理无关,而是由 client 和 server 共同控制。串联过程在 Go 中可以用两行代码简单搞定:

  1. go io.Copy(conn1, conn2) 
  2. io.Copy(conn2, conn1) 

TE Trailer Transfer-Encoding和请求传输的方式有关。代理在读取客户端请求时应该确保正确处理了 chunked 的传输方式后再删除这几个头,由代理自行决定在发往目的服务器时要不要使用分块传输。类似的还有 Content-Encoding,这个决定的是请求的压缩方式,也应该在代理端被科学的处理掉。好在传输方式这几个头在 Go 的标准库中都有实现,对开发者基本都是透明的,开发者可以直接使用而无需关心具体的逻辑。

Websocket 与 HTTP2

前面提到过 Upgrade,这里再简单说说。这个头常用于从 HTTP 转换到 Websocket 或 HTTP2 协议。对于 Websocket,被动扫描时可以不关注,所以可以直接放行。这里放行的意思是不再去解析,而是类似 Tunnel 那种,单纯的进行数据转发。对于 HTTP2 ,我们可以拒绝这一转换,使得数据协议始终用 HTTP,也算是一个偷懒的捷径。

当然,如果想要做的完善些,就需要套用一下这两种协议的解析,伪装成 Websocket server 或 HTTP2 server,然后做中间人去获取传输数据,有兴趣可以看一下 Python 的 MitmProxy 的实现。

离完美的差距

回顾刚才说的一些要点,这里的被动代理实现其实并不完美,主要有这两点:

第一点是隧道模式下,我们强行判定了以 0×16 开头的就是 TLS 流量,协议千千万,这种可能有误判的。其次我们认为 TLS 层下的应用协议一定是 HTTP,这也是不妥的,但对于被动扫描这种场景是足够了。

另一点是隧道模式下证书的签发流程不够完美。如果你用过虚拟主机,或者尝试过在同一地址同一端口上运行多个 HTTP 服务,那一定知道 nginx 中的 server_name 或是 apache 的 VirtualHost。服务器收到 HTTP 请求后会去查看请求的 Host 字段,以此决定使用哪个服务。TLS 模式下有所不同,因为 TLS 握手时服务器没法读取请求,为此 TLS 有个叫 SNI(Server Name Indication)的拓展解决了这个问题,即在 TLS 握手时发送客户端请求的域给服务器,使得在同一 ip 同一端口上运行多个 TLS 服务成为了可能。回到被动代理这,之前我们签证书用的域是从 CONNECT 的 HOST 中获取的,其实更好的办法是从 TLS 的握手中读取,这样就需要自行实现 TLS 的握手过程了,具体可以参考下 MitmProxy 的实现。

https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读