HTTP/2协议支持两种传输方式:TLS加密的h2和非加密的h2c。其中h2c允许客户端通过HTTP/1.1的Upgrade机制从明文HTTP连接直接升级到HTTP/2明文通信。问题在于,升级过程会改变连接上后续字节的解析方式。如果链路中存在反向代理、WAF或负载均衡器,它们与后端服务器对升级边界的判定不一致,攻击者就能利用这个缝隙发起请求走私。这类攻击通常被称为H2C走私攻击,核心在于让前端认为连接已经进入HTTP/2帧解析阶段,而后端仍停留在HTTP/1.1请求解析阶段,或者相反。

H2C升级流程如何制造解析分歧
HTTP/1.1规范定义了Upgrade机制,允许客户端通过普通HTTP连接请求切换到另一个协议。客户端发送一个包含Connection: Upgrade和Upgrade: h2c的请求,服务器若同意则返回101状态码,随后双向通信切换为HTTP/2帧格式。下面是一个最简单的升级请求示例:
GET / HTTP/1.1 Host: ipipp.com Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: AAMAAABkAAQCAAAAAAIAAAAA
在这个请求中,Connection头告知中间设备需要逐跳处理Upgrade和HTTP2-Settings字段,Upgrade头指定目标协议为h2c,HTTP2-Settings头携带HTTP/2 SETTINGS帧的Base64编码。如果后端返回101 Switching Protocols,连接就会被切换。问题在于,这个切换动作是逐跳的,也就是说,代理和后端是否同时切换、谁先切换,并不一定一致。
如果前端代理支持HTTP/2明文并自行返回101,而它没有把升级状态正确传递给后端,或者后端根本没有处理Upgrade头,那么前端开始按HTTP/2帧解析后续数据,而后端仍然按HTTP/1.1解析。这种解析状态的不一致为走私攻击提供了条件。攻击者可以精心安排升级请求和后续字节,使得前端把这些字节解释为HTTP/2帧,而后端把它们解释为新的HTTP/1.1请求。
H2C走私攻击的典型利用路径
理解利用路径之前,需要先明确攻击者控制连接上的字节布局。在HTTP/1.1中,请求边界通常由Content-Length或Transfer-Encoding头决定。而在HTTP/2中,数据被封装成帧,每个帧有长度字段,不再依赖HTTP/1.1的头来切分。H2C走私攻击正是利用了这种从文本协议到二进制帧协议的边界转换差异。
一种常见场景是:前端代理配置为支持h2c,当它看到带有Upgrade: h2c的请求时,会返回101并进入HTTP/2帧解析模式;但后端服务是纯HTTP/1.1实现,始终按HTTP/1.1处理。攻击者先发送一个合法的升级请求,完成与前端代理的协议切换。之后,攻击者发送一个HTTP/2 DATA帧,帧内填充一个完整的HTTP/1.1请求,例如针对管理接口的GET请求。前端代理将该DATA帧当作合法HTTP/2数据转发给后端,而后端因为没有升级,会将帧载荷直接当作新的HTTP/1.1请求来解析。这样一来,攻击者成功向后端注入了一个额外请求,绕过了前端基于HTTP/2流的安全检查。
为了更直观地说明,可以看一个简化后的HTTP/2 DATA帧结构。该帧的载荷并不复杂:
Length: 0x002d Type: DATA (0x0) Flags: END_STREAM (0x1) Stream Identifier: 1 Payload: GET /admin HTTP/1.1 Host: internal Connection: close
这里展示的是一种逻辑表示,实际线上数据是二进制编码。攻击者通过HTTP/2客户端库或手动构造帧,把HTTP/1.1请求放入DATA帧的载荷中。前端代理按照HTTP/2帧格式转发,后端则将载荷解析为新请求。如果后端认为该请求来自内部可信代理,就可能绕过访问控制。
另一种路径相反:前端不支持h2c,仅按HTTP/1.1转发,而后端支持升级。攻击者发送一个带有Upgrade: h2c头的请求,并在请求末尾追加额外数据。前端将整个数据当作HTTP/1.1转发,后端处理时识别到Upgrade并返回101,然后切换到HTTP/2解析模式。此时后端会把追加的额外数据当成HTTP/2帧来解析。攻击者可以构造这些帧,让后端在某个流上处理恶意数据,例如触发后端向内部地址发起请求。这种场景对攻击者的协议构造能力要求更高,但同样真实存在。
检测与防御H2C走私攻击
检测H2C走私攻击的关键,在于观察代理与后端对Upgrade请求和后续数据的处理是否一致。可以使用协议模糊测试工具,发送包含额外内容的h2c升级请求,观察返回状态码和响应头的变化。如果前端返回101,而后端响应异常,或出现请求错位、超时、重复处理等现象,就可能存在漏洞。
防御层面最直接的做法是禁用非加密的h2c。对于面向公网的服务,统一使用TLS加密的h2,既减少了协议升级带来的解析歧义,又能避免明文连接在中间设备上被篡改。如果业务确实需要明文HTTP/2,应确保整条链路中的代理、负载均衡器和后端都使用支持HTTP/2的实现,并且对升级请求的处理策略完全一致。
同时,代理层应拒绝包含正文或异常头组合的Upgrade请求。对于Connection头中出现Upgrade且Upgrade值为h2c的请求,可以强制丢弃HTTP2-Settings头之外的所有数据,避免攻击者夹带走私内容。此外,在代理配置中关闭自动协议升级,或者不允许代理与后端之间使用明文h2c,也能显著缩小攻击面。
最后,定期开展针对请求走私的测试,包括经典的CL.TE、TE.CL以及本文讨论的h2c升级混淆场景。使用专业测试工具对内部测试环境进行探测,确认代理与后端的协议解析边界一致,才能有效避免这类隐蔽攻击。