写点什么

解密协议层的攻击——HTTP 请求走私

发布于: 2021 年 02 月 19 日

最近一直在研究一些比较有意思的攻击方法与思路,在查阅本地文档的时候(没错,本地,我经常会将一些有意思的文章但是没时间看就会被我保存 pdf 到本地),一篇 2019 年 Black hat 的议题——HTTP 请求走私,进入我的视野,同时我也查阅到在 2020 Blackhat 中该攻击手法再次被分析。我对此产生浓厚学习兴趣,于是便有了这篇文章。


HTTP 请求走私是一种 HTTP 协议的攻击利用方法,该攻击产生的原因在于 HTTP 代理链中 HTTP Server 的实现中存在不一致的问题。

时间线


  • 2004 年,@Amit Klein 提出 HTTP Response Splitting 技术,为 HTTP Smuggling 攻击雏形;

  • 2005 年,第一次被 @Watchfire 所提出, 并对其进行了详细介绍;

  • 2016 年,DEFCON 24 上,@regilero 在他的议题——Hiding Wookiees in HTTP 中在对前面报告进行丰富与扩充;

  • 2019 年,Blackhat USA 上,PortSwigger 的 @James Kettle 在其议题——HTTP DESYNC ATTACKS SMASHING INTO THE CELL NEXT DOOR 中对当前网络环境进行了分析,同时在其利用上加入 chunked 技术,对现有攻击面进行了拓展;

  • 2020 年,Blackhat USA 上,@Amit Klein 在其议题——HTTP Request Smuggling in 2020 中最新变种手法进行分析,同时对各类环境场景下进行了分析。


漏洞利用场景分析


HTTP 协议请求走私并不像其他 web 攻击手法那么直观,而是在更加复杂的网络环境中,因不同服务器基于不同的 RFC 标准实现的针对 HTTP 协议包的不同处理方式而产生的一种安全风险。


在对其漏洞进行分析前,首先需要了解目前被广泛使用的 HTTP 1.1 协议特性——Keep-Alive、Pipeline 技术。


简单来说,在 HTTP 1.0 及其以前版本的协议中,在每次进行交互的时候,C/S 两端都需要进行 TCP 的三次握手链接。而如今的 web 页面大部分主要还是由大量静态资源所组成。如果依然按照 HTTP 1.0 及其以前版本的协议设计,会导致服务器大量的负载被浪费。于是在 HTTP 1.1 中,增加了 Keep-Alive、Pipeline 技术。


KEEP-ALIVE


根据 RFC7230 规范中 section-6.3 可以得知,HTTP 1.1 中默认使用 persistent connections 方式。其实现手法是在 HTTP 通信包中加入 Connection: Keep-Alive 标识:在一次 HTTP 通信后不会关闭 TCP 连接,而在后续相同目标服务器请求中复用该空闲的 TCP 通道,避免了由于新建 TCP 连接产生的时延和服务器资源消耗,提升用户资源访问速度。


PIPELINE


而在 Keep-Alive 中后续又有了 Pipeline 机制,这样客户端就可以像流水线一样不用等待某个包的响应而持续的向服务器发包。而服务器也会遵循先进先出原则对客户端请求进行响应。



如图,我们可以看到相比于 no pipelining 模式,pipelining 模式下服务器在响应时间上有了很大的提升。


现如今,为了提高用户浏览速度、加强服务稳定性、提升使用体验以及减轻网络负担。大部分厂商都会使用 CDN 加速服务或负载均衡 LB 等部署业务。当用户访问服务器静态资源时,将直接从 CDN 上获取详情,当存在真正服务器交互时,才会与后端服务器产生交互。如图所示:



但是,该模式中 reverse proxy 部分将长期与 back-end 部分通信,一般情况下这部分连接会重用 TCP 通道。通俗来说,用户流量来自四面八方,user 端到 reverse proxy 端通信会建立多条 TCP 通道,而 rever proxy 与 back-end 端通信 ip 固定,这两者重用 TCP 连接通道来通信便顺理成章了。


在这种场景下,当不同服务器实现时参考的 RFC 标准不同时,我们向 reverse proxy 发送一个比较模糊的 HTTP 请求时,因为 reverse proxy 与 back-end 基于不同标准进行解析,可能产生 reverse proxy 认为该 HTTP 请求合法,并转发到 back-end,而 back-end 只认为部分 HTTP 请求合法,剩下的多余请求,便就算是夹带走私的 HTTP 请求了。当该部分对正常用户的请求造成了影响之后,就实现了 HTTP 走私攻击。如图所示:深色为正常请求,橙色为走私请求,绿色为正常用户请求。一起发包情况下,走私的请求内容被拼接到正常请求中。



CHUNKED 数据包格式


分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许 HTTP 的数据可以分成多个部分。


如下图所示,为 jdcloud.com 未进行数据包进行 chunked。


当对 jdcloud.com 进行分块时,如下图所示。



常见攻击


注:后续文章中所提到 CL=Content-Length,TE=Transfer-Encoding,如需使用 burpsuite 进行数据包调试时,需去除 Repeater 中 Update Content-Length 选项。


场景 1:GET 请求中 CL 不为 0 情况


主要指在 GET 中设置 Content-Length 长度,使用 body 发送数据。当然这里也不仅仅限制与 GET 请求中,只是 GET 的理解比较典型,所以我们用在做例子。


RFC7230 Content-Length 部分提到:


For example, a Content-Length header field is normally sent in a POST request even when the value is 0 (indicating an empty payload body). A user agent SHOULD NOT send a Content-Length header field when the request message does not contain a payload body and the method semantics do not anticipate such a body.


在最新的 RFC7231 4.3.1 GET 中也仅仅提了一句:


A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.

 

从官方规范文档可以了解到:RFC 规范并未严格的规范 Server 端处理方式,对该类请求的规范也适当进行了放松,但是也是部分情况。由于这些中间件没有一个严格的标准依据,所以也会产生解析差异导致 HTTP Smuggling 攻击。


  • 构造数据包

GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 44\r\n
GET /secret HTTP/1.1\r\nHost: example.com\r\n\r\n
复制代码

由于 GET 请求,服务器将不对 Content-Length 进行处理,同时因为 Pipeline 的存在,后端服务器会将该数据包视为两个 GET 请求。分别为:


请求——1

GET / HTTP/1.1\r\nHost: example.com\r\n
复制代码


请求——2

GET /secret HTTP/1.1\r\nHost: example.com\r\n
复制代码

这就导致了请求走私。


场景 2:CL-CL


在 RFC7230 的第 3.3.3 节中的第四条中,规定当服务器收到的请求中包含两个 Content-Length,而且两者的值不同时,需要返回 400 错误。


If a message is received without Transfer-Encoding and with either multiple Content-Length header fields having differing field-values or a single Content-Length header field having an invalid value, then the message framing is invalid and the recipient MUST treat it as an unrecoverable error. If this is a request message, the server MUST respond with a 400 (Bad Request) status code and then close the connection. If this is a response message received by a proxy, the proxy MUST close the connection to the server, discard the received response, and send a 502 (Bad Fielding & Reschke Standards Track [Page 32] RFC 7230 HTTP/1.1 Message Syntax and Routing June 2014 Gateway) response to the client. If this is a response message received by a user agent, the user agent MUST close the connection to the server and discard the received response.


但是某些服务器并没遵循规范进行实现,当服务器未遵循该规范时,前后服务器都不会响应 400。可能造成代理服务器使用第一个 Content-Length 获取长度,而后端按照第二个 Content-Length 获取长度。


  • 构造数据包

POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 8\r\nContent-Length: 7\r\n
12345\r\na
复制代码

这个时候,当后端服务器接受到数据包时,Content-Length 长度为 7。实际上接受到的 body 为 12345\r\n,而我们前面所提到的,代理服务器会与后端服务器重用 TCP 通道,这个时候 a 就会拼接到下一个请求。这个时候如果存在一个用户发起 GET 请求。则该用户 GET 请求实际为:


aGET / HTTP/1.1\r\nHost: example.com\r\n
复制代码


同时该用户也会收到一个类似 aGET request method not found 的报错响应,其实这样就已经实现了一次 HTTP 协议走私攻击,对正常用户造成了影响,而且后续可以扩展成类似于 CSRF 的攻击方式。


但是两个 Content-Length 这种请求包还是太过于理想化了,一般的服务器都不会接受这种存在两个请求头的请求包,但是在 RFC2616 的第 4.4 节中,规定:


The transfer-length of a message is the length of the message-body as it appears in the message; that is, after any transfer-codings have been applied. When a message-body is included with a message, the transfer-length of that body is determined by one of the following (in order of precedence):

If a Transfer-Encoding header field (section 14.41) is present and has any value other than "identity", then the transfer-length is defined by use of the "chunked" transfer-coding (section 3.6), unless the message is terminated by closing the connection.


也就是说,当 Content-Length 与 Transfer-Encoding 同时被定义使用时,可忽略 Content-Length。也就是说当 Transfer-Encoding 的加入,两个 Content-Length 并不影响代理服务器与后端服务器的响应。


场景 3:CL-TE


这里的情况是指代理服务器处理 Content-Length,后端服务器会遵守 RFC2616 的规定,处理 Transfer-Encoding 的情况(这里也就是场景 2 后边所提到的情况)。


  • 构造数据包

POST / HTTP/1.1\r\n Host: example.com\r\n User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: en-US,en;q=0.5\r\n Connection: keep-alive\r\n Content-Length: 6\r\n Transfer-Encoding: chunked\r\n \r\n0\r\n\r\nG
复制代码


  • 因前后服务器规范不同,解析如下:


请求——1 (代理服务器的解析)

POST / HTTP/1.1\r\n Host: example.com\r\n User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: en-US,en;q=0.5\r\n Connection: keep-alive\r\n Content-Length: 6\r\n Transfer-Encoding: chunked\r\n \r\n0\r\n\r\nG
复制代码


请求——2 (代理服务器的解析)

G


其中 G 被留在缓存区中,当无其他用户请求时候,该数据包会不会产生解析问题,但 TCP 重用情况,当一个正常请求过来时候。将出现如下情况:

GPOST / HTTP/1.1\r\nHost: example.com\r\n....
复制代码

这个时候 HTTP 包,再一次通过 TCP 通道进行走私。


场景 4:TE-CL


即代理服务器处理 Transfer-Encoding 请求,后端服务器处理 Content-Length 请求。


  • 构造数据包


POST / HTTP/1.1\r\n Host: example.com\r\n User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: en-US,en;q=0.5\r\n Content-Length: 4\r\n Transfer-Encoding: chunked\r\n \r\n 12\r\nGPOST / HTTP/1.1\r\n\r\n0\r\n\r\n
复制代码


由于 Transfer-Encoding 遇到 0\r\n\r\n 才结束解析。此时后端将解析 Content-Length,真正到达后端数据将为:

POST / HTTP/1.1\r\n2Host: example.com\r\n3User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0\r\n4Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n5Accept-Language: en-US,en;q=0.5\r\n6Content-Length: 4\r\n7\r\n812\r\n
复制代码


同时将出现第二个数据包:


GPOST / HTTP/1.1\r\n\r\n0\r\n\r\n
复制代码


当收到存在两个请求头的请求包时,前后端服务器都处理 Transfer-Encoding 请求头,这确实是实现了 RFC 的标准。不过前后端服务器毕竟不是同一种,这就有了一种方法,我们可以对发送的请求包中的 Transfer-Encoding 进行某种混淆操作(这里主要指 Content-Length),从而使其中一个服务器不处理 Transfer-Encoding 请求头。从某种意义上还是 CL-TE 或者 TE-CL。


  • 构造数据包

POST / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-length: 4\r\nTransfer-Encoding: chunked\r\nTransfer-encoding: cow\r\n\r\n5c\r\nGPOST / HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\nx=1\r\n0\r\n\r\n
复制代码


攻击场景分析


使用 PortSwigger 的实验环境环境进行实际攻击演示。

复用 TCP 进行管理员操作


*靶场链接:

https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-cl-te


This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding. There's an admin panel at /admin, but the front-end server blocks access to it.

To solve the lab, smuggle a request to the back-end server that accesses the admin panel and deletes the user carlos.


实验目的:访问 admin 页,并利用认证对 carlos 用户进行删除。


  • SETP 1、因直接访问/admin 目录被提示拦截,同时题目提示 CL.TE。这里通过构造 CL.TE 格式数据包,尝试访问。/admin 路由。




  • SETP 2、访问提示管理员界面只允许为本地用户访问,尝试直接访问 localhost,并获取到删除用户路由地址。



  • SETP 3、通过构造请求访问即可,最终再次访问/admin 显示页面已经没有删除 carlos 用户选项。




结合业务获取用户请求包-1


*链接:

https://portswigger.net/web-security/request-smuggling/exploiting/lab-reveal-front-end-request-rewriting


This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding.

There's an admin panel at /admin, but it's only accessible to people with the IP address 127.0.0.1. The front-end server adds an HTTP header to incoming requests containing their IP address. It's similar to the X-Forwarded-For header but has a different name.

To solve the lab, smuggle a request to the back-end server that reveals the header that is added by the front-end server. Then smuggle a request to the back-end server that includes the added header, accesses the admin panel, and deletes the user carlos.


实验目的同上,不过这里在前端服务器做了限制。不支持 chunked。同时前端到后端做了检查,在 headers 中自定义了一个类似于 X-Forwarded-For 的头。


  • SETP1、通过页面 search 处直接构造走私数据包,在页面返回中间服务器到后端服务器数据包内容(走私数据包长度当前为 200,若实际场景中显示不全则可通过增加 CL 长度解决),获取到 X-uNiqsg-Ip 头。同时,这里之所以选择 search 处,主要是因为该处在页面存在输出。



  • SETP2、通过伪造同样请求发包即可。



结合业务获取用户请求包-


*链接:

https://portswigger.net/web-security/request-smuggling/exploiting/lab-capture-other-users-requests


This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding.

To solve the lab, smuggle a request to the back-end server that causes the next user's request to be stored in the application. Then retrieve the next user's request and use the victim user's cookies to access their account.


实验目的:通过写页面的方式,获取下一个请求数据包中 cookie 数据。


  • SETP1、发现 post?postId=路由下存在写页面操作,通过修改数据包。



  • SETP2、访问当前页面查看 website 处即可获取到下一个请求包的数据。(这里同样也可以控制下一个请求包数据在评论区,只需将最后一个评论参数 comment 放至最后即可)



反射 XSS


*链接:

https://portswigger.net/web-security/request-smuggling/exploiting/lab-deliver-reflected-xss


This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding.

The application is also vulnerable to reflected XSS via the User-Agent header.

To solve the lab, smuggle a request to the back-end server that causes the next user's request to receive a response containing an XSS exploit that executes alert(1).


应用场景:当业务存在反射型 XSS 时,可通过缓存投毒的方式在其他用户页面写入脏数据。


SETP1、 进入任意评论区发现页面存在 userAgent 回显,通过走私协议修改 userAgent 即可。




进行缓存投毒


*链接:

https://portswigger.net/web-security/request-smuggling/exploiting/lab-perform-web-cache-poisoning


This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding. The front-end server is configured to cache certain responses.

To solve the lab, perform a request smuggling attack that causes the cache to be poisoned, such that a subsequent request for a JavaScript file receives a redirection to the exploit server. The poisoned cache should alert document.cookie.


应用场景:劫持下一用户请求页面。(实际场景中可劫持跳转至钓鱼等页面)


  • SETP1、缓存注入修改 Host 为恶意请求。


关于防御


从前面的案例我们可以看到 HTTP 请求走私的危害性,那么如何防御呢?


  • 禁用代理服务器与后端服务器之间的 TCP 连接重用。

  • 使用 HTTP/2 协议。

  • 前后端使用相同的服务器。


但是这些修复方法又存在一些现实困难:


  • HTTP/2 推行过于困难,尽管 HTTP/2 兼容 HTTP/1.1。

  • 取消 TCP 重用将增大服务器负载,服务器资源吃不消。

  • 使用相同的服务器,在一些厂商其实也很难实现。其主要原因还是前后端实现标准不一致的问题。


那么没有解决方案了嘛?


其实不然,上云就是个很好的方案。云主机、CDN、WAF 都统一实现编码规范,可以很好地避免该类问题的产生。

参考链接

*https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/DEF%20CON%2024%20-%20Regilero-Hiding-Wookiees-In-Http.pdf

*https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn

*https://regilero.github.io/english/security/2019/10/17/securityapachetrafficserverhttp_smuggling/

*https://paper.seebug.org/1048

*https://tools.ietf.org/html/rfc2616

*http://blog.zeddyu.info/2019/12/05/HTTP-Smuggling/

*https://tools.ietf.org/html/rfc7230

*https://tools.ietf.org/html/rfc7231


推荐阅读


欢迎点击【京东科技】,了解开发者社区

更多精彩技术实践与独家干货解析

欢迎关注【京东科技开发者】公众号


发布于: 2021 年 02 月 19 日阅读数: 15
用户头像

拥抱技术,与开发者携手创造未来! 2018.11.20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东科技开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
解密协议层的攻击——HTTP请求走私