你的扫描器可以绕过防火墙么(三)
背景
How to Break Through Cloud-Based WAF 提到 “post请求体过大时会绕过某些waf”。
刚好今天看到一个用此方法绕过的案例:Java反序列化数据绕WAF之加大量脏数据
这个绕过姿势记得在2017年就有人分享过,因为我对”因为性能原因所以导致waf放行”这个解释有点疑问,所以研究了一下。
本文主要研究:
- 为什么请求体过大时会绕过waf的防护?
- 什么场景下可以使用这种绕过姿势攻击到后端服务?
- 怎么测试?
以下研究的waf都是基于openresty实现的。
分析过程
openresty怎么读请求体?
获取请求 body 的代码:
1
2
3ngx.req.read_body()
local data = ngx.req.get_body_data()
-- local file_name = ngx.req.get_body_file()根据 openresty文档 得知
如果请求体存放到了临时文件,就应该调用
ngx.req.get_body_file
接口来获取临时文件名。如果请求体存放到内存中,就应该调用
ngx.req.get_body_data
接口来获取请求体。什么时候请求体会存放到临时文件呢?
有两个配置选项决定nginx是否会把请求体放到临时文件:
client_body_buffer_size:如果请求体大小超过此值,就会把整个请求体或者请求体的一部分写到一个临时文件。
client_body_in_file_only:此选项直接决定nginx是否将请求体放到文件中。
为什么请求体过大时会绕过waf的防护?
请求体过大,超过 client_body_buffer_size 配置的值时,请求体会写到临时文件。
而很多waf不处理临时文件。相当于拿到的请求体会为空。比如曾经很火的 ngx_lua_waf 就没有临时文件相关操作。
那为啥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:如果请求体大小超过此设置值,就会返回 413 (Request Entity Too Large) 错误。 此配置为0表示不限制请求体大小。
再比如php的post-max-size:如果请求体大小超过此设置值,
$_POST
和$_FILES
会为空。所以满足以下条件时,应该可以利用成功:
请求体大小超过waf client_body_buffer_size配置限制,且waf没有处理临时文件
请求体大小未超过后端服务的大小限制
怎么测试
客户端的测试代码如下,需要看情况调整big_str的大小
1 | import requests |
总结
本文并没有研究waf厂商是怎么解决这个问题的,比如pdf提到的stream模式是怎么实现的、stream模式是否可以绕过。
绕过waf防护的原因是请求体过大时会被存放到”临时文件”中,而不处理”临时文件”是出于io、内存等性能考虑。
这种绕过姿势还需要”请求体大小未超过后端服务的大小限制”才能利用成功。
另外 “post请求过大为什么会绕过waf”还有两个可能的原因:
- payload刚好被”tcp分片”
- 正则回溯次数超过限制