0x01 前言
HTTP走私是bug bounty 项目中经常被关注的点。通过HTTP走私,攻击者可以访问内部的服务器甚至是获得各种提权的机会。当前HTTP/1.1被广泛应用,但也暴露出一些问题,比如容易出现请求走私,使用HTTP/2可能是一个解决走私的一个方案。但HTTP/2就能完全防止走私吗?本文就对HTTP/2 h2c 走私进行一个简要的研究。
0x02 HTTP/2协议与H2C交换
通常HTTP/2协议在第一个HTTP请求之前,使用h2字符串进行标识,h2c是在Web协议从HTTP/1.1升级到HTTP/2的步骤中使用的标头。根据RFC-7540文档的描述,仅当客户端和服务器均支持HTTP/2时,才能使用HTTP / 2协议。如果有一个如下请求:
GET / HTTP/1.1
Host: test.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
如果是不支持HTTP/2的服务器,则响应:
Server ignored
如果是支持HTTP/2的服务器,则响应:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ HTTP/2 connection ...
通过阅读RFC-7540文档发现,HTTP/2通信与websocket通信有些类似。客户端(Web浏览器)询问服务器是否支持HTTP/2与Web服务器通信,并相应地决定使用HTTP/2还是HTTP/1.x通信。
HTTP/2通信是在第7层(应用程序)中执行的协议,使用TCP连接,由于它与现有的HTTP通信不同,101 Switching protocol
会像websocket一样检查是否支持之后,才使用协议转换器。并且HTTP/2使用与HTTP/1.1相同的 “http “和 “https “URI方案,HTTP/2共享相同的默认端口号,比如在http->80,https->443上与HTTP相同。
An HTTP/2 connection is an application-layer protocol running on top of a TCP connection ([TCP]). The client is the TCP connection initiator.
HTTP/2 uses the same “http” and “https” URI schemes used by HTTP/1.1. HTTP/2 shares the same default port numbers: 80 for “http” URIs and 443 for “https” URIs. As a result, implementations processing requests for target resource URIs like “http://example.org/foo“ or “https://example.com/bar“ are required to first discover whether the upstream server (the immediate peer to which the client wishes to establish a connection) supports HTTP/2.
The means by which support for HTTP/2 is determined is different for “http” and “https” URIs. Discovery for “http” URIs is described in Section 3.2. Discovery for “https” URIs is described in Section 3.3.
当HTTP/1.x要升级到HTTP/2,标识符、HTTP2-Settings
标头和Upgrade标头需要出现在http请求中。标识符的类型包括HTTP的h2c
和HTTPS的h2
。当Upgrade: h2c
时,则以纯文本形式传递HTTP/2:
GET / HTTP/1.1
Host: test.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
在服务器支持HTTP/2时,它将101 Switching protocol
转发到客户端并建立TLS连接(HTTP/2)与客户端进行通信。在这种情况下,使用TLS-ALPN协议。在此过程中,使用APLN扩展名,客户端向服务器提供版本列表,然后服务器选择一个版本。同理,使用https时,HTTP/2选择h2
。
当直接使用HTTP/2时,通过TLS-ALPN进行协议协商之后,进行TLS连接。
0x03 H2C走私
许多Web服务都使用反向代理。在此过程中,当需要进行101 Switching
时,代理服务器将充当中介,无需任何操作。通过阅读RFC文档和TLS中关于的HTTP/2配置文档,里面声明只有明文连接才可以使用h2c
升级,并且转发时不应包含HTTP2-Settings
头。
在RFC7540#section-3.2.1中指出:
HTTP2-Settings = token68
A server MUST NOT upgrade the connection to HTTP/2 if this header
field is not present or if more than one is present. A server MUST
NOT send this header field.
在http2-spec中还指出:
3.3 Starting HTTP/2 for “https” URIs
A client that makes a request to an “https” URI uses TLS [TLS12] with the application-layer protocol negotiation (ALPN) extension [TLS-ALPN].
HTTP/2 over TLS uses the “h2” protocol identifier. The “h2c” protocol identifier MUST NOT be sent by a client or selected by a server; the “h2c” protocol identifier describes a protocol that does not use TLS.
Once TLS negotiation is complete, both the client and the server MUST send a connection preface (Section 3.5).
在TLS上使用HTTP/2时,被告知使用h2
协议标识符,而不是h2c
。正如上一节提到的,h2c
是一个用在http上的标识,而h2
则是用于https的标识。如果代理通过TLS将h2c
转发到后端进行协议升级,会出现什么情况呢?
我个人理解:在反向代理环境中,后端服务器仅知道客户端是Cleartext还是TLS(具有h2c
和h2
等标识),因此它将TLS连接确定为HTTP,并在TLS连接上创建TCP隧道。在这种情况下,由于客户端不是HTTP,但仍可以通过TLS使用现有连接。换句话说,由于它是已连接的连接而不是HTTP通信,因此不受Proxy的ACL策略的影响,并且由于TCP Tunnel中的请求可以进行HTTP操作,因此可以访问被阻止的资源。个人感觉整个走私行为与WebSocket连接走私非常相似。
具体走私流程如下:
1 .客户端将HTTP/1.1升级请求发送到反向代理(发送了错误的标头)
2 .代理将请求转发到后端,后端返回101 Swiching协议的响应,并准备接收HTTP/2通信
3 .代理从后端服务器收到101响应时,将创建TCP隧道
4 .客户端从代理接收到101响应时,将重新用现有TCP连接并执行HTTP/2初始化
5 .客户端使用HTTP/2多路复用,发送针对受限资源的违法请求
6 .由于代理服不监视TCP通信(HTTP通过策略),因此它违法求发送到受限页面
7 .服务器响应,转发到TLS隧道,实现走私
0x04 如何检测
以下代理可能存在这一走私问题:
1 .默认支持(默认存在问题):
HAProxy v2.2.2
Traefik v2.2
Nuster v5.2
2 .需要配置(只有不恰当设置才会有问题):
AWS ALB / CLB
NGINX
Apache
Squid
Varnish
Kong
Envoy
Apache Traffic Server
那么如何进行检测测试呢?这里提供h2csmuggler工具
安装配置:
$ git clone https://github.com/BishopFox/h2csmuggler
$ cd h2csmuggler
$ pip3 install h2
扫描:
$ python3 h2csmuggler.py --scan-list urls.txt --threads 5
获取内部端点:
$ python3 h2csmuggler.py -x https://edgeserver -X POST -d '{"user":128457 "role": "admin"}' -H "Content-Type: application/json" -H "X-SYSTEM-USER: true" http://backend/api/internal/user/permissions
暴破端点:
$ python3 h2csmuggler.py -x https://edgeserver -i dirs.txt http://localhost/
这个过程中使用了HTTP/2的多路复用。复用是HTTP/2的主要功能,这意味着可以同时请求多个资源,可以理解为Connection: keep-alive , pipeline
的改进版。
获取AWS 源数据 api:
$ python3 h2csmuggler.py -x https://edgeserver -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" http://169.254.169.254/latest/api/token`
更多使用细节参见:https://github.com/BishopFox/h2csmuggler
0x05 如何预防
对于HTTP请求走私/ WebSocket连接走私可能有多种应对对策,但从原则上讲,按照RFC文档中的说明,限制在TLS连接中使用h2c
升级应该是最为有效的方法。当然,如果可能的话,也可以通过限制代理服务器中传递标头,仅处理由服务使用的标头来减小风险。
我个人认为,重要的是要防止使用未使用的标头,限制可以查看其他主机(例如host,x-forward-for等)的标头,以使私有路径无法被直接访问。与所有走私活动一样,防护方了解每一步消息传递的差异比依靠单纯补丁更有预防效果。对于这类通过请求走私或请求伪造攻击进行的任意用户控制的请求,应当维持纵深防御策略,减少架构中走私标头的重要性,在后端识别和拒绝可疑请求,才能有助于减小这类攻击的影响。
参考文献
https://github.com/BishopFox/h2csmuggler
https://www.hahwul.com/2019/10/30/websocket-connection-smuggling/
https://zh.wikipedia.org/wiki/HTTP/2
https://tools.ietf.org/html/rfc7540