前言

最近在实战中遇到太多Fortieth的WAF——FortiWeb。做啥都被拦,这能忍?摇人!和一个大佬沟通了下,他说用分块传输可以绕过,好的,关键词已获取,然后就去查询了相关的资料,直接实战操作,可行!

分块传输编码原理

分块传输编码Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许HTTP由应用服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分。分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供。

通常情况下,HTTP的响应消息体(message body)是作为整包发送到客户端的,用头(Content-Length)来表示消息体的长度,这个长度对客户端非常重要,因为对于持久连接TCP并不会在请求完立马结束,而是可以发送多次请求/响应,客户端需要知道哪个位置才是响应消息的结束,以及后续响应的开始,因此Content-Length显得尤为重要,服务端必须精确地告诉客户端(message body)的长度是多少,如果(Content-Length)比实际返回的长度短,那么就会造成内容截断,如果比实体内容长,客户端就一直处于pendding状态,直到所有的(message body)都返回了请求才结束。

Web2.0的出现使得网页变得丰富多彩,内容也比早期的网页复杂很多,这样就会遇到一个问题,对于一个复杂的页面来说,如果是等到消息体完全创建好之后再计算出Content-Length返回给客户端的话,在客户端那边会有一个漫长的等待过程,而对于用户来说,一个页面的所能容忍的等待时间不超过3秒,因此如何让响应内容尽可能早的让用户看到是HTTP协议要考虑的问题。

分块传输编码Transfer-Encoding)就是这样一种解决方案:它把数据分解成一系列数据块,并以多个块发送给客户端,服务器发送数据时不再需要预先告诉客户端发送内容的总大小,只需在响应头里面添加Transfer-Encoding: chunked,以此来告诉浏览器我使用的是分块传输编码,这样就不需要Content-Length了,这就是分块传输编码Transfer-Encoding的作用。

HTTP 1.1引入分块传输编码带来的好处可参阅:维基百科

在消息头中指定Transfer-Encoding: chunked就表示整个response将使用分块传输编码来传输内容,一个完整的消息体由n个块组成,并以最后一个大小为0的块为结束。每个非空的块包括两部分,分别为:块的长度(用十六进制表示)后面跟一个CRLF(\r\n),长度并不包括结尾的回车换行符。第二部分就是数据本身,同样以CRLF结束。最后一个分块长度值必须为0以及CRLF组成,对应的分块不包含任何数据,但必须有两个空行。

举例一个最简单的例子,向服务端POST一个内容为id=123456的数据包:

1
2
3
4
5
6
7
8
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['id'])) {
$id = $_POST['id'];
echo "ID: " . htmlspecialchars($id);
} else {
echo "No ID received!";
}
?>

可以尝试一下,如果关闭burp在repeater选项里面自带的content-length补全功能,然后去掉Content-Length,就无法接收到id的值。

接下来我们将其改为分块传输的方式:

通过上图可以看到,即使没有Content-Length,也可以采用分块传输的方式,分多少块,每块长度都不唯一,但最后的结尾需要一个长度为0内容为空的块(内容为两个空行)

限制

分块传输限制也非常的明显,只在HTTP协议1.1版本(HTTP/1.1)中提供,且只能在POST方法中使用。但是存在一些Web应用框架存在不支持POST请求的情况,像Laravel有着严格的路由限制,可以通过 Route::get()和Route::post()来定义允许的请求类型。若请求方法不匹配所定义的路由,Laravel会返回405 “Method Not Allowed”错误。例如,若一个只允许GET请求的路由存在SQL注入,对该路由发送POST请求则会被拒绝,这样子就无法使用分块传输进行绕过。

利用分块传输绕过WAF:实战(已脱敏)

已经确定目标存在.git泄露,正常访问/.git/网页403

访问/.git/index触发FortiWeb拦截页面,Attack ID: 20000008

在FortiWeb日志中,攻击ID 20000008通常表示系统检测到基于已知特征规则的潜在恶意行为。此攻击ID记录的情况包括参数、URL或数据包中的元素匹配已知的攻击模式。具体来说,可能涉及的攻击类型有跨站脚本(XSS)、SQL注入、通用攻击、信息泄露,甚至可能检测到恶意机器人等活动。

利用分块传输绕过,但由于该方法只能在POST方法中使用,且必须有请求体,所以要先将GET请求转为POST请求,并插入任意数据,最后再进行chunk编码。

但是有一些如Imperva的,360等比较好的WAF已经对传输编码的分块传输做了处理,可以把分块组合成完整的HTTP数据包,这时直接使用常规的分块传输方法尝试绕过的话,会被WAF直接识别并阻断,像下图所示。

使用注释扰乱分块数据包,在[RFC7230]中查看到有关分块传输的定义规范,分块传输可以在长度标识处加上分号“;”作为注释。

几乎所有可以识别Transfer-Encoding数据包的WAF,都没有处理分块数据包中长度标识处的注释,导致在分块数据包中加入注释,WAF就识别不出这个数据包了。

当然,有大佬已经将这个方法写成了burp插件Chunked coding converter,为了方便在burp上快速的进行分块传输测试,主要有以下功能:

  • 在Burp Repeater套件上可对数据包进行快速chunked解码编码

  • 自动化对Burp的Proxy,scanner,spider等套件的数据包进行编码

  • 可设置分块长度,是否开启注释

接下来就是通过GithackChunked coding converter插件的联动

首选需要先稍微改动下Githack工具的代码,更改请求方式GET->POST,并插入任意请求体:

1
2
3
4
5
6
7
8
import urllib

def _request_data(url):
post_data = {'test': 'test'}
data = urllib.urlencode(post_data)
request = urllib2.Request(url, data, {'User-Agent': 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_8; zh-cn) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27'})
#request = urllib2.Request(url, None, {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X)'})
return urllib2.urlopen(request, context=context).read()

再配置Chunked coding converter对Proxy套件的数据包进行编码

最后对绕过WAF进行漏洞利用展示:


持续学习……

参考资料:
WAF分块传输绕过
利用分块传输协议绕waf
利用分块传输吊打所有WAF
在HTTP协议层面绕过WAF
编写Burp分块传输插件绕WAF

看到了,但暂时还未用到:
Java反序列化数据绕WAF之延时分块传输
WAF延时分块传输绕过
WAF HTTP协议覆盖+分块传输组合绕过
Bypass waf(四个层次)