(最近发现自己的基础特别差,恶补一下,这边用问答的方式来学习)
核心问题1:
1、HTTP请求走私(CL.TE / TE.CL)为什么会产生?前端代理和后端服务器的解析差异在哪?
具体展开:
(以下均为个人理解:查了资料以后,自己手写的理解;有问题欢迎指正)
需要搞懂的基本概念:
前端服务器&后端服务器:
你= 客户端(浏览器)
服务员= 前端代理(比如 Nginx、CDN节点、负载均衡器)。你只跟服务员说话。
厨师= 后端服务器(真正处理业务的程序,比如 Tomcat、Node.js)。
你的请求是先给“服务员”,服务员再转交给“厨师”。
HTTP/1.0和HTTP/1.1去区别:
HTTP/1.0是每次请求建通道后,请求完又把通道拆掉,再次请求又需要重新建通道,很慢。
HTTP/1.1(长连接):请求后保持一条TCP通道,可以连续发多个请求。
CL和TE的区别:
既然建立了长连接那怎么知道第一个请求何时结束,第二请求何时开始呢?就引入了CL和TE两种方法。
方法A:Content-Length(简称 CL)
例如:请求包中有Content-Length: 13,后端就往后读13个字节,读完这个请求就结束。
方法B:Transfer-Encoding: chunked(简称 TE)
简单理解就是:每次传输不知道总长度,一块一块发,每块前面标明这块多大。当我发一个大小为0的块时,表示结束。
一般格式如下:
5\r\n (表示下一块有5个字节) Hello\r\n (具体的5个字节) 0\r\n (大小为0,表示全部发完了!) \r\n (结束的换行符)RFC标准规定:如果一个请求同时带了 CL 和 TE,应该以 TE 为准,忽略 CL。
漏洞理解:
假设“服务员”(前端代理)和“厨师”(后端服务器)用的是不同公司写的软件(比如前端是 Nginx,后端是 Tomcat)。
如果故意发了一个畸形(不规范)的请求,里面同时包含了 CL 和 TE,前端和后端对于请求的大小可能就会产生分歧。
详细拆解 CL.TE 和 TE.CL
只是前后端的数据大小判断方式不同:
CL.TE = 前端用 Content-Length,后端用 Transfer-Encoding。
TE.CL = 前端用 Transfer-Encoding,后端用 Content-Length。
可以利用这两种方法构造“幽灵请求”
场景一:CL.TE(前端认CL,后端认TE)
攻击者发送如下原始数据给前端:
POST / HTTP/1.1 Host: example.com Content-Length: 13 Transfer-Encoding: chunked 0 SMUGGLED前端知道大小是13会全部传给后端,但是后端看到0,觉得请求已经结束。
后面的SMUGGLED后端认为它是下一个新请求的开头! 此时,如果有正常用户发来了一个请求GET /admin HTTP/1.1...。 后端会把滞留的SMUGGLED和正常用户的请求拼起来,变成:SMUGGLEDGET /admin HTTP/1.1...,从而执行攻击者想要的操作。
场景二:TE.CL(前端认TE,后端认CL)
攻击者构造如下请求:
POST / HTTP/1.1 Host: example.com Content-Length: 3 Transfer-Encoding: chunked 6\r\n GOTCHA 0\r\n \r\n前端遵守TE,知道第一块是6,第二块是0,可能直接原样转发给后端。(也可能会把 chunked 格式去掉,重新计算真实长度并加上新的 CL 头)
后端却忽略TE只看CL,只认Content-Length: 3。 后端只读了前3个字节(即6\r\n),就认为这个请求结束了。
会产生这样的情况:前端认 TE 读完了整个请求,但后端只认 CL 读了一点点就停了,剩下的全变成了“幽灵”。
前后端解析差异产生的原因
畸形 TE 头(容错处理不同): 黑客故意把
Transfer-Encoding: chunked写成奇怪的样子,比如:Transfer-Encoding: chunked2 (多了个2) Transfer-Encoding: CHUNKED (大写) Transfer-Encoding : chunked (冒号前加了空格) Transfer-Encoding: \tchunked (加了制表符)有些前端软件比较智能,会自动修复(规范化)这些错误,认出它是 TE;但有些后端软件也许比较死板,一看格式不对,直接忽略它,退回到使用 CL(fallback to CL)。这就产生了差异。
前端重写 Body: 有时候前端代理在转发时,会主动把 chunked 编码解开,转换成普通的带有 CL 的请求发给后端。如果转换过程中没处理好,也会导致两边认知不同。
H2.CL 变体(HTTP/2 降级):HTTP/2 本身是没有 CL 和 TE 这种定界问题的(它有自己的帧结构)。但是很多架构是:客户端到前端用 HTTP/2,前端到后端降级成 HTTP/1.1。 前端在把 H2 翻译成 H1 的时候,如果不小心带上了错误的 CL 头,而后端又信了这个 CL,就会产生新的走私漏洞(叫 H2.CL)。(这个我目前还没学会,这边先了解一下)
危害
窃取他人请求体:黑客把幽灵请求构造为“把数据发到我的服务器”,当正常用户访问时,用户的密码/Cookie会被拼接到幽灵请求里发给黑客。
绕过前端 ACL(访问控制):前端代理可能拦截了/admin路径,但黑客通过走私,让后端直接收到/admin请求,绕过了前端的防火墙。
缓存投毒(Cache Poisoning):黑客走私一个恶意响应,让前端的 CDN 缓存下来,导致所有访问该网站的用户都看到黑客伪造的页面。
防御
前端拒绝歧义请求:前端代理如果发现一个请求里既有 CL 又有 TE,或者 TE 格式畸形,直接返回 400 Bad Request,拒绝转发。不要试图去“猜”或“修复”。
全链路 HTTP/2 端到端:HTTP/2 协议从根本上解决了这个问题(使用二进制分帧,不需要 CL 和 TE 来定界)。如果前端到后端也用 HTTP/2,就不会有这种转换导致的差异了。
补充概念:这边需要区别软件开发中的前后端分离
| 比较维度 | 网络架构(如HTTP走私) | 软件开发(如前后端分离) |
|---|---|---|
| “前端”指的是什么 | 反向代理服务器、网关、CDN、Nginx | 用户看到的界面(网页、App、Vue/React代码) |
| “后端”指的是什么 | 真正处理业务的源站服务器(Tomcat等) | 提供数据和逻辑的服务器程序(Java/Python/数据库) |
| 他们怎么交流 | 通过底层的 TCP/HTTP 协议转发字节流 | 通过约定的 API 接口(HTTP + JSON)交换数据 |
| 常见面试题 | HTTP走私、负载均衡、跨域配置、缓存穿透 | 前后端分离优缺点、RESTful API、JWT鉴权、跨域(CORS) |