h2c走私:通过HTTP/2明文(h2c)请求走私

Posted 启明星辰金睛安全研究团队

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了h2c走私:通过HTTP/2明文(h2c)请求走私相关的知识,希望对你有一定的参考价值。

HTTP走私越来越多出现在我们的视野中,通过HTTP走私,我们可以访问内部的服务器甚至是获得各种提权的机会。使用HTTP/2或者是HTTP/3可能是一个解决走私的解决方案,但是从短期来看HTTP/1.1并不会消失,而且从HTTP/1.1中也我们发现了更多的信息。本文主要演示了通过HTTP/2 without TLS(h2c)将HTTP/1.1升级到HTTP/2并且可以Bypass反向代理的访问控制,并且保持一个TCP长连接,导致HTTP请求不受反向代理的控制直接到达后端服务器。

背景:HTTP/1.1 升级和代理

首先介绍一下HTTP/1.1如何升级到HTTP/2以及代理如何实现升级请求。
Upgrade请求头可以用于升级HTTP/2,它最常用的场景是升级WebSocket连接。当代理服务器代理了一条升级过后的请求,代理服务器简单的将TCP请求代理到后端。注意这个过程中代理服务器不会检查代理的内容,而且也不会实施访问控制。
h2c的升级过程如下:客户端在HTTP/1.1中发起升级请求,当客户端收到了Switching Protocals响应头后,客户端会使用同一条TCP连接并且根据新协商的协议(h2c)传输数据。如下图:

nginx收到了后端发来的101响应头后,将响应转发给客户端,完成h2c的升级。之后将保持客户端与服务器的TCP长连接,并且不会监控内容(这个过程和websocket协议升级相同)
先前有大佬@0ang3el演示如何使用WebSocket走私以与后端服务器保持TCP长连接,来规避代理服务器的访问控制。那么是不是可以设计一个基于HTTP的TCP通道呢,寻找更灵活的走私方法。


H2C规范

通常HTTP/2协议是在第一个HTTP请求之前,使用h2字符串进行标识,但是HTTP/2也可以使用HTTP/1.1升级,使用h2c标识,用于明文通信。下面是一个使用HTTP/1.1升级HTTP/2的示例:
GET / HTTP/1.1
Host: www.example.com
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
Connection: Upgrade, HTTP2-Settings
HTTP2-Settings是连接参数的Base64编码,根据RFC规范,只有明文连接才可以使用h2c升级,并且转发时不应包含HTTP2-Settings头

阅读RFC文档后,提出了三个问题:

  1. 如果代理服务器正在终止TLS,此时在HTTP请求中发送了H2C升级请求,那么后端服务器怎么知道我是尝试通过TLS升级h2c?

  2. 如果代理不支持h2c,那他可以转发客户端的h2c升级请求吗?

  3. 后端服务器同意升级h2c并且代理服务器代理该连接,能否绕过代理服务器的访问控制?


接下来使用Hyper-2的HTTP2库,创建了客户端进行测试。


测试

我设置Nginx为443端口的TLS终止,添加一个根路由类似WebSocket的proxy_pass来支持h2c的升级。并且添加了一段访问控制,不允许访问/flag。下面是配置详情:
server {
listen 443 ssl;
server_name localhost;
 
ssl_certificate /usr/local/nginx/conf/cert.pem;
ssl_certificate_key /usr/local/nginx/conf/privkey.pem;
 
location / {           
proxy_pass http://backend:9999;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
 
location /flag {
deny all;
}
对于后端服务器,创建了一个简单的Golang服务器,该服务器支持h2c升级:
// Lightly modified example from:https://github.com/thrawn01/h2c-golang-example
package main
 
import (
"fmt"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"net"
"net/http"
"os"
)
 
func checkErr(err error, msg string) {
if err == nil {
return
}
fmt.Printf("ERROR: %s: %s\n", msg, err)
os.Exit(1)
}
 
func main() {
h2s := &http2.Server{}
 
handler := http.NewServeMux()
handler.HandleFunc("/", func(whttp.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %v, http: %v",r.URL.Path, r.TLS == nil)
})
 
handler.HandleFunc("/flag", func(whttp.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You got the flag!");
})
 
server := &http.Server{
Addr: "0.0.0.0:9999",
Handler: h2c.NewHandler(handler, h2s),
}
 
fmt.Printf("Listening [0.0.0.0:9999]...\n")
checkErr(server.ListenAndServe(), "whilelistening")
}

如设置的一样,无法直接访问/flag

h2c走私:通过HTTP/2明文(h2c)请求走私

下图展示了请求的过程:

h2c走私:通过HTTP/2明文(h2c)请求走私

现在,使用我的脚本来启动TLS升级,我们就可以访问受限制的路由。

这是发生的事情:

分解一下刚刚发生的事情:
1. 客户端将HTTP/1.1升级请求发送到反向代理Nginx
2. Nginx将升级请求转发到后端,后端返回"101 SwitchingProtocols"响应,并准备接收HTTP2通信。
3. Nginx从后端收到101响应后,将TCP连接升级到非托管的TCP隧道中。
4. 客户端从代理接收到101响应后,重用现有TCP连接并与服务器交换HTTP/2的初始化请求。
5. 客户端使用HTTP/2的多路复用,向本应受到访问控制的的/flag发送请求。
6. 不再进行访问控制的Nginx,将请求转发到后端服务器。
7. 服务器响应该HTTP请求。

如上所示,就成功地绕过了代理的访问控制来访问内部的HTTP接口!

我们还可以通过一些其他非加密通道上实施这种攻击。如果代理不支持h2c升级,而只是将客户端的h2c升级请求转发到后端,则这种攻击也有可能在非加密通道上成功。
我还做了一个测试,确认了即使和服务器之间间隔了多个反向代理,这种走私方法仍然有效。只要所有代理成功传递了必要的请求头就可以执行攻击,一条TCP长连接通道会穿越所有中间的代理服务器。
这种走私可以使用HTTP/2通道发送任意数量的请求。同样,根据我们先前的研究,HTTP请求走私可实现多种攻击,比如:伪造内部请求头,访问受限的HTTP API等等。
 
但是还有一个疑问:Nginx配置太具体了。在什么情况下才会这样配置Nginx呢? 下面是默认情况下会转发h2c请求头的HAProxy,Traefik和Nuster配置。
HAProxy /Nuster
mode http
frontend fe
bind *.8080
default_backend be1
backend be1
server s1 backend:80
Traefik
http:
  routers:
    to-test:
      rule:"PathPrefix (`/`)"
      service:test
services:
    test:
     loadBalancer:
        servers:
        - url:http://backend:80
注意:由于Traefik在代理的连接字符串上不包含HTTP-2-Settings,这可能会导致攻击在某些h2c实现中失败。

有哪些服务器支持h2c?

因为h2c具有减少带宽的能力,所以它非常适合用于低延迟的内部网络通信,尤其是企业服务间的微服务通信,而且避免了TLS 的管理和(有争议但经常被提及的)性能开销。
因此,流行的Web框架通常支持启用h2c升级。但是支持升级并不代表默认支持,可能还需要手动开启。
假设前端代理配置不安全,则微服务中h2c的升级请求具有更大的风险。


防护措施

如果需要支持WebSocket请求,那么代理服务器上应该配置仅仅支持WebSocket请求升级,如果后端没有WebSocket请求的需求,那么代理服务器不应该转发Upgrade请求头。

哪些代理服务器存在这个问题

默认支持(默认存在问题)
• HAProxy v2.2.2
• Traefik v2.2
• Nuster v5.2

需要配置(只有不恰当设置才会有问题)
• AWS ALB / CLB
• NGINX
• Apache
• Squid
•Varnish
• Kong
• Envoy
• Apache Traffic Server


总结

请求走私和其他代理绕过漏洞突出了对于对安全性的边缘服务器访问控制的过分依赖的问题。对于这类通过请求走私或请求伪造攻击进行的任意用户控制的请求,应当维持纵深防御策略,减少架构中走私标头的重要性,在后端识别和拒绝可疑请求,才能有助于减少该攻击技术的影响。

参考链接

本文由金睛安全研究团队整理并翻译,不代表金睛安全研究团队任何观点和立场。来源:https://labs.bishopfox.com/tech-blog/h2c-smuggling-request-smuggling-via-http/2-cleartext-h2c

以上是关于h2c走私:通过HTTP/2明文(h2c)请求走私的主要内容,如果未能解决你的问题,请参考以下文章

Nginx 是不是支持以明文形式从 http/1.1 升级 http/2 (h2c)

h2csmuggler:一款隐蔽性极强的HTTP2明文通信工具

HTTP Request Smuggling 请求走私

springboot搭建http2服务器和h2c服务器 h2 的http/https 请求服务器

Tomcat H2C请求混合漏洞预警(CVE-2021-25122)

关于HTTP请求走私攻击