前言
HTTP请求走私是一种干扰网站处理HTTP请求序列方式的技术,使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。请求走私大多发生于前端服务器和后端服务器对客户端传入的数据理解不一致的情况。这是因为HTTP规范提供了两种不同的方法来指定请求的结束位置,即 Content-Length 和 Transfer-Encoding标头。
预备知识
持久连接
定义
持久连接的出现背景是为了解决TCP中三次握手、慢启动的特点,为了提高HTTP的性能,长连接由此诞生。HTTP协议加入了相应的机制,通过Connection: keep-alive 这个头部来实现,服务端和客户端都可以使用它告诉对方在发送完数据之后不需要断开 TCP 连接。
对于持久连接,浏览器没法通过连接是否关闭来界定请求或响应实体的边界,所以浏览器不清楚我们是否发送完所有数据,因此为了解决这个问题,科学家引入Content-length
Content-length(实体长度)
服务器可以通过Content-Length的长度信息,判断响应实体已结束。但如果content-length的长度与实际长度不匹配,则会出现以下问题。
- Content-Length > 实际长度
服务器/客户端读取到信息结尾后,会等待下一个字节,会无响应直到超时
- Content-Length < 实际长度
首次请求的信息会被截取,比如参数为param=d0g3,content-length=8,那么这次请求的信息会被截取为:param=d0。
特别值得注意的是,如果第二次请求如果发送一样的信息,服务器将会抛出异常:Request method ‘g3POST’ not supported。产生的原因就是因为持久连接的特性,造成解析混乱。
Transfer-Encoding: chunked(分块编码)
在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。
漏洞原理
处于减轻服务器负担的目的,很多网站都使用了CDN加速服务,比如在源站的前面加上一个有缓存功能的反向代理服务器。当多个服务器理解的数据不一致时,就会出现有些服务器认为content-length的长度有效,有些以Transfer-Encoding为准,这样超出的长度就会被拼接到下一次请求,导致漏洞。
漏洞利用
1.CL不为0的get请求
产生原因
前端代理服务器允许请求携带请求体,而后端服务器不允许请求携带请求体,会直接忽略掉请求中的cl头,这就有可能导致请求走私。
利用语句
1 | GET / HTTP/1.1\r\n |
前端服务器收到请求,读取CL判断这是一个完整的请求,然后转发给后端服务器,由于后端服务器不对CL进行处理,它就认为收到了两个请求。
1 | 第一个 |
2.CL-CL
产生原因
如果服务器不严格实现规范^1,比如CDN按照第一个CL的值对请求进行处理,而源站服务器按照第二个CL的值进行处理。
利用语句
1 | POST / HTTP/1.1\r\n |
此时源站服务器的缓冲区还剩下一个字母a,它会认为a是下一个请求的一部分,只是没有传输完毕,若此刻恰好有一个正常用户对服务器进行了请求,那么正常用户的请求就会被拼接到字母a的后面,导致访问失败。
3.CL-TE
产生原因
CDN只处理CL这一请求头,而后端服务器会遵守RFC2616^2的规定,忽略掉CL,处理TE的请求头。
利用语句
1 | POST / HTTP/1.1\r\n |
由于前端处理CL,所以它以为请求的长度为6,内容为
1 | 0\r\n |
而后端服务器处理TE时读取到0\r\n\r\n时,就认为已经读取到结尾了,可是剩下的字母G被留在了缓冲区,当后续请求来的时候,就会产生如CL-CL那种报错。