How to Break Through Cloud-Based WAF[1] 提到 "post请求体过大时会绕过某些waf"。
刚好今天看到一个用此方法绕过的案例:Java反序列化数据绕WAF之加大量脏数据
这个绕过姿势记得在2017年就有人分享过,因为我对"因为性能原因所以导致waf放行"这个解释有点疑问,所以研究了一下。
本文主要研究:
以下研究的waf都是基于openresty实现的。
openresty怎么读请求体?
获取请求 body[2] 的代码:
ngx.req.read_body()
local data = ngx.req.get_body_data()
-- local file_name = ngx.req.get_body_file()
根据 openresty文档[3] 得知
如果请求体存放到了临时文件,就应该调用
ngx.req.get_body_file
接口来获取临时文件名。如果请求体存放到内存中,就应该调用
ngx.req.get_body_data
接口来获取请求体。
什么时候请求体会存放到临时文件呢?
有两个配置选项决定nginx是否会把请求体放到临时文件:
client_body_buffer_size[4]:如果请求体大小超过此值,就会把整个请求体或者请求体的一部分写到一个临时文件。
client_body_in_file_only[5]:此选项直接决定nginx是否将请求体放到文件中。
为什么请求体过大时会绕过waf的防护?
请求体过大,超过 client_body_buffer_size 配置的值时,请求体会写到临时文件。
而很多waf不处理临时文件。相当于拿到的请求体会为空。比如曾经很火的 ngx_lua_waf[6] 就没有临时文件相关操作。
那为啥waf作者没有处理临时文件呢?
可能是忘了,也可能觉得损耗性能。这里的性能我理解主要是与 io、内存有关。
在openresty中读文件可以有两个api:
ngx_io.open,此api是非阻塞的
io.open,此api是阻塞的
如果使用io.open
读"临时文件",整个nginx worker就会卡在这个读文件步骤中,肯定会导致整个集群能服务的最大并发数变低。
如果使用lua-io-nginx-module
模块的ngx_io.open
非阻塞api来代替io.open
阻塞api来读文件,虽然不会阻塞整个nginx worker,但"读文件io操作"还是要和磁盘打交道,需要中断和多次内存拷贝,还是有性能损耗。
并且,如果服务是将文件一次性读取到内存中时,那"不怀好意"的人多搞几个这样"post请求体过大"的请求,就有可能导致机器内存耗尽。
什么场景下可以使用这种绕过姿势?
即使waf放行了,也需要后端支持这种超大的请求体。而不少服务对请求体大小是有限制的。
比如nginx的client_max_body_size[7]:如果请求体大小超过此设置值,就会返回 413 (Request Entity Too Large) 错误。此配置为0表示不限制请求体大小。
再比如php的post-max-size[8]:如果请求体大小超过此设置值,$_POST
和$_FILES
会为空。
所以满足以下条件时,应该可以利用成功:
请求体大小超过waf client_body_buffer_size配置限制,且waf没有处理临时文件
请求体大小未超过后端服务的大小限制
客户端的测试代码如下,需要看情况调整big_str的大小
import requests
host = "a.baidu.com"
url = "http://127.0.0.1:9998/a.php"
payload = "../../../"
big_str = "A"*1024*1024*200 # 200M
headers = {"Host": host, "Content-Type": "application/x-www-form-urlencoded"}
data = "a=%s&b=%s" % (big_str, payload)
res = requests.post(url, data=data, headers=headers)
print(res.content.decode("utf8"))
print(res.status_code)
本文并没有研究waf厂商是怎么解决这个问题的,比如pdf提到的stream模式是怎么实现的、stream模式是否可以绕过。
绕过waf防护的原因是请求体过大时会被存放到"临时文件"中,而不处理"临时文件"是出于io、内存等性能考虑。
这种绕过姿势还需要"请求体大小未超过后端服务的大小限制"才能利用成功。
另外 "post请求过大为什么会绕过waf"还有两个可能的原因:
payload刚好被"tcp分片" 正则回溯次数超过限制
How to Break Through Cloud-Based WAF: https://github.com/pyn3rd/papers/blob/master/KCon_2019_WAF.pdf
[2]获取请求 body: https://moonbingbing.gitbooks.io/openresty-best-practices/content/openresty/get_req_body.html
[3]openresty文档: https://github.com/openresty/lua-nginx-module#ngxreqread_body
[4]client_body_buffer_size: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size
[5]client_body_in_file_only: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only
[6]ngx_lua_waf: https://github.com/loveshell/ngx_lua_waf/blob/master/waf.lua
[7]client_max_body_size: http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
[8]post-max-size: https://www.php.net/manual/en/ini.core.php#ini.post-max-size