HTTP请求走私
前置知识:HTTP 1.1
协议特性——Keep-Alive&Pipline
在HTTP 1.0
之前,客户端每进行一次HTTP请求,就需要同服务器创建一个TCP连接,而在此之前,大多数网页都为静态网页,而随着网络发展,网页中增加了例如js css
等的图片资源,为了减少服务器压力,从而在HTTP 1.1
中增加了Keep-Alive 和 Pipeline
两个特性
-
Keep-Alive
: 在请求头中增加一个特殊的请求头Connection : Keep-Alive
,告诉服务器接受完HTTP请求后,不关闭TCP的连接,使得后续继续进行HTTP请求时,重用该TCP连接,使得只需要进行一次TCP握手。从而达到了减少服务器的压力例如,对baidu.com进行抓包,就可以在请求头中找到
Connection : Keep-Alive
Pipeline
:出现了Keep-Alive
之后,后续产生了Pipeline
,使得客户端可以在没有接收到上一个请求的响应时就可以直接发送下一个请求,服务器根据时间顺序依次对请求进行处理响应,将响应和请求严格对照后进行返回(浏览器默认不启用,但是一般服务器都支持)
此外,为了使得用户访问速度更快,避免服务器产生过大负荷,很多网站还是用了CDN加速服务,使用反向代理(例如:Nginx),当请求静态资源时,直接从反向代理服务器的缓存获取,当请求动态资源时,反向服务器将请求分发给后端服务器
一般来说,反向代理服务器与后端服务器的通信就采用了重用TCP连接
漏洞产生原理:不同服务器对相同结果的不同响应
当向我们想代理服务器发送一个较为模糊的HTTP请求时,由于两者实现方式不同,代理服务器只认为这是一个HTTP请求,直接转发给后端服务器,后端服务器经过解析后,将其中一部分视为正常请求,而剩下一部分则成功走私,当对正常用户的请求造成了影响之后,就是先了HTTP走私攻击
而导致其中差异的原因是有于对Content-Length
与Transfer-Encoding
处理的不同顺序,Content-Length
与Transfer-Encoding
都被作为POST数据进行传输,一般将二者的处理优先规则有着简写的规则“
- CL-TE: 代表反向代理服务器以
Conent-Length
优先处理,后台服务器以Transfer-Encoding
优先处理 - TE-CL: 代表反向代理服务器以
Transfer-Encoding
优先处理,后台服务器以Conent-Length
优先处理
但是关于CLTE的解析顺序在RFC7230规定了相关优先顺序,但任然有很多中间件并没有按照此标准规范进行实现,从而导致了解析的差异性
关于Transfer-Encoding
Transfer-Encoding是一种被设计同来支持7-bit传输服务安全传输二进制数据的字段。在HTTP中Transfer-Encoding
主要用来已制定的编码形式进行编码,在HTTP/1.1中引入,在HTTP/2中取消
主要的几种编码:
chunked compress deflate gzip identity
在HTTP中主要利用的为chunked
进行分块传输
例如,当利用chunked进行传输:parar inrnrnchuanks
则其数据包可以构造为
POST /xxx HTTP/1.1
Host: xxx
Content-Type: text/plain
Transfer-Encoding: chunked
3rn
parrn
2rn
arrn
drn
inrnrnchunksrn
0rn
rn
第一个3表示后跟着三个字节的数据,及par
,根据RFC标准,利用rn表述chunk数据,数据字节大小用16进制进行表示例如:(包含空格,数据中的rn一共算两字节,共13字节,利用十六进制d进行表示)
drn
inrnrnchunksrn
在数据最后,利用:
0rn
rn
表示chunked部分数据结束
CL != 0
请求体:
GET / HTTP/1.1rn
Host: example.comrn
Content-Length: 44rn
GET / secret HTTP/1.1rn
Host: example.comrn
rn
攻击原理:前端服务器检查CL,而后端不进行检查
当前端对CL进行检查时,将CL下的数据去全部解析为GET请求时附带的请求体,将全部转发给后端服务器
后端服务器接收到以上数据包后不对CL进行检查,由于Pipeline
的解析机制,将以上数据包认为是两个间隔时间很小的请求,分别对shangxia两个请求进行解析,从而达到两个走私:
第一个:
GET / HTTP/1.1rn
Host: example.comrn
Content-Length: 44rn
第二个:
GET / secret HTTP/1.1rn
Host: example.comrn
rn
CL-CL
请求包中包含两个Conten-Length
攻击原理:RFC2616
的第4.4节中,规定:如果收到同时存在Content-Length和Transfer-Encoding这两个请求头的请求包时,在处理的时候必须忽略Content-Length
,代理服务器按照第一个CL进行数据处理,后端对第二个数据进行处理
请求体:
POST / HTTP/1.1rn
Host: example.comrn
Content-Length: 8rn
Content-Length: 7rn
12345rn
a
中间服务器按照第一个解析后,将全部数据转发给后端服务器,而后端服务器按照第二个进行解析,在读完12345rn
后,解析结束,遗留下一个a
,当下一个正常请求发送,例如用户发送:
GET /index.html HTTP/1.1rn
Host: example.comrn
此时,服务器将二者进行拼接,使得后端服务器真正解析到的数据为:
aGET /index.html HTTP/1.1rn
Host: example.comrn
通常来说,服务器将会返回aGET request method not found
的一个博爱错。从而实现走私攻击
CL-TE
原理:代理服务器之处理Conten-Length
这一请求头,后端服务器遵守RFC2616
的规定,忽略掉Content-Length
,处理Transfer-Encoding
。
例如数据包:
POST / HTTP/1.1rn
Host: example.comrn
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0rn
Connection: keep-alivern
Content-Length: 6rn
Transfer-Encoding: chunkedrn
0rn
rn
a
中间代理服务器按照Content-Length进行解析,将请求体中全部数据视为整体,全部转发给后端服务器,后端服务器对Transfer-Encoding进行处理,识别到
0rn
rn
数据解析结束,留下一个a
存储在缓冲区中,拼接到下一次的请求中,实现走私
TE-CL
前端代理服务器处理Transfer-Encoding
这一请求头,而后端服务器处理Content-Length
请求头。
POST / HTTP/1.1rn
Host: example.comrn
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn
Content-Length: 4rn
Transfer-Encoding: chunkedrn
rn
12rn
aPOST / HTTP/1.1rn
rn
0rn
rn
前端服务器处理Transfer-Encoding
,当其读取到0rnrn
时,认为是读取完毕了,此时这个请求对代理服务器来说是一个完整的请求,然后转发给后端服务器,后端服务器处理Content-Length
请求头,当它读取完12rn
之后,就认为这个请求已经结束了,后面的数据就认为是另一个请求了,也就是
aPOST / HTTP/1.1rn
rn
0rn
rn
达到报错的目的
TE-TE
前后端服务器都处理Transfer-Encoding
请求头,这确实是实现了RFC的标准。不过前后端服务器毕竟不是同一种,这就有了一种方法,我们可以对发送的请求包中的Transfer-Encoding
进行某种混淆操作,从而使其中一个服务器不处理Transfer-Encoding
请求头。从某种意义上还是CL-TE
或者TE-CL
。
走私类型判断
-
基于时间进行判断:
构造不同类型的数据包进行发送,若类型符合,造成走私后,走私的数据会存储在缓存区,等待下一个数据包到达后再进行拼接,从而导致明显的时间差异。例如判断是否为CL-TE类型的时候,发送数据包:
POST / HTTP/1.1 Host: example.com Transfer-Encoding: chunked Content-Length: 4 /r/n 1 A X
若类型符合,X将成功走私,存储在缓存区,等待下一个请求的到达
-
基于响应的差异:
例如为了判断是CL-TE还是TE-CL,可以发送:
POST /search HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 50 Transfer-Encoding: chunked /r/n e q=smuggling&x= 0 /r/n GET /404 HTTP/1.1 Foo: x
若为CL-TE,则攻击成功,与下一次的请求拼接,使得请求包变异返回404
HTTP2降级走私
HTTP2学习
HTTP2引入了二进制分帧,多路复用,头部压缩,服务器推送等高级功能,显著提高了网络通信的效率和速度。
其中二进制分帧将通信信息不在座位一个整体进行发送,而是分解为多个较小的帧,每个帧携带特定类型的数据,如头部信息,负载数据等
相关术语解释:
- Stream(流):已建立的连接中的双向字节流,可以携带一条或多条消息
- Message(消息):映射到逻辑请求或响应消息的完整帧序列
- Frame(帧):帧是HTTP/2中最小的通信单元,每个单元包含一个帧头,它至少标识该帧所属的流,所有通信都是通过一个TCP连接进行的,该连接可以承载任意数量的双向流,而每个流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息,每个消息都是一个逻辑HTTP消息,例如:请求或响应,由一个或多个帧组成,帧是携带特定类型数据(例如:HTTP报头、消息负载等)的最小通信单元,来自不同流的帧可以被交织,然后经由每个帧的报头中嵌入的流标识符被重组
HTTP的请求头在HTTP2中便会被拆分为多个HEADER frame ,请求体便会被拆分为多个 DATA frame,最后添加上一个END_STREAM标志位,表示数据包结束。帧与帧之间相较于以前,界限更明确,不需要Conten-Length进行区别,无论为多少都能被正常解析
但是,当HTTP/2流量通过代理时,代理可能需要将其转换回HTTP/1.x以与不支持HTTP/2的旧服务器通信。在这个转换过程中,如果代理没有正确地重建请求边界,或者在将HTTP/2帧转换为HTTP/1.x请求时出现错误,就可能引入请求走私的漏洞。而实际情况中大多数CDN厂商都支持HTTP/2,但是很多后端服务并不支持,所以当转化流量这一步出现问题就会导致HTTP2降级请求走私漏洞。
走私成因:
一个畸形HTTP数据包:
POST / HTTP/2.0
Host: www.example.com
Content-Length: 1
aGET / HTTP/1.1
Host: www.example.com
在HTTP2中的结构大致为
其中,冒号开头的是在HTTP/2中引入的一种称为伪头字段的特殊头部字符,以冒号开头以区别于正常的HTTP头部字段,它们必须在所有常规头部字段之前发送,并且每个HTTP/2请求或响应只能包含一份。
伪头字段包括:
:method
:这个字段包含HTTP请求的方法(例如GET或POST)。:scheme
:表示请求的URI方案(例如http或https)。:path
:请求的路径信息(例如/index.html)。对于HTTP/2连接来说,:path
不能是空的,至少要是一个正斜杠("/")。:authority
:包含了请求的目标主机和端口信息(例如www.example.com:443)。在HTTP/1.1中,这通常由“Host”头部字段提供。:status
:仅在响应中出现,包含了响应的状态码(例如200或404)。
伪头字段是HTTP/2协议的关键组成部分,它们使得HTTP/2能够更有效地封装和传输HTTP语义。通过这种方式,HTTP/2保持了与HTTP/1.x系列的兼容性,同时提高了性能和效率。
而对于HTTP/1.x协议来说它不太符合规范,因为HTTP/1.x解析请求的边界需要受Content-Length
的控制,而实际的body长度和Content-Length
标注的长度不相等。如果按照Content-Length
来读取请求,先读取到一个POST请求
POST / HTTP/2.0
Host: www.example.com
Content-Length: 1
a
接着解析后面的数据,会发现也是一个合格的HTTP请求数据包,这样就解析到了第二个请求。
GET / HTTP/1.1
Host: www.example.com
那么就像上面提到的,如果有HTTP/2代理HTTP/1.x的场景就有可能出现,代理接收到一个请求,转发给后端服务器被处理成两个请求的情况。就像下面这张图一样,红色的方块代表走私的请求,经过HTTP/2代理的处理,转给HTTP/1.1的队列里被当成独立的新请求了。