绿冰壶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的标志图:
绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图1

URL-Path

描述的是项目或者模块中资源绝对路径(不是相对路径)

Host

主机地址

Parameter-name

参数名

Parameter

参数

Header-name

头文件名

header

http请求头。标注请求文件类型

cookie-name

cookie名(类似于键)

cookie

cookie值(类似于键名)

version

浏览器版本

Method

请求类型

Request-Body

请求体

WAF自定义基础|通过nginx配置文件抵御攻击

验证浏览器行为

简单做一个比喻。当前疫情紧张时期,假如某个地区因疫情物资紧张,居民采购蔬菜被限量。而有些贪心的人,派了一堆歪果仁(没有国籍不享受政策)来冒领蔬菜。怎么防止这种事情呢?工作人员在发蔬菜之前,会给领取者一张纸,上面写着“天王盖地虎”,如果那人能念出纸上的字,那么就是享受政策的人,给红包,如果你不能念出来,那么请自觉离开。
是的,在这个比喻中,居民就是浏览器,歪果仁就是攻击器,我们可以通过鉴别cookie功能(念纸上的字)的方式来鉴别他们。
下面是通过nginx配置cookie鉴别功能的代码:

  1. if($cookie_say != "twgdh")
  2. {
  3. add_header Set-Cookie
  4. "say=hbnl"
  5. ;
  6. rewrite .*
  7. "$scheme://$host$uri"
  8. redirect;
  9. }

让我们看下这几行的意思,当cookie中say为空时,给一个设置cookie say为twgdh的302重定向包,如果访问者能够在第二个包中携带上cookie值,那么就能正常访问网站了,如果不能的话,那他就只能永远活在302中。
那假如贪心的人 发现了规律,他们发给每个歪果仁一个录音笔,重复播放”天王盖地虎,天王盖地虎“此时我们怎么办呢?
这时,工作人员的对策是这样做的,要求领取者出示有自己名字的户口本,并且念出自己的名字,“我是xxx,天王盖地虎”。于是一群只会嗡嗡叫着“天王盖地虎”的歪果仁又被撵回去了。

  1. if ($cookie_say != "twhdjl$remote_addr")
  2. {
  3. add_header Set-Cookie
  4. "say=twgdh$remote_addr";
  5. rewrite .*
  6. "$scheme://$host$uri"
  7. redirect;
  8. }

这样的写法和前面的区别是,不同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不可逆)。

  1. rewrite_by_lua '
  2. local
  3. say = ngx.md5(
  4. "opencdn"
  5. .. ngx.var.remote_addr)
  6. if
  7. (ngx.var.cookie_say ~= say)
  8. then
  9. ngx.header[
  10. "Set-Cookie"
  11. ] =
  12. "say="
  13. .. say
  14. return
  15. ngx.redirect(ngx.var.scheme ..
  16. "://"
  17. .. ngx.var.host .. ngx.var.uri)
  18. end

通过这样的配置,攻击者便无法事先计算这个cookie中的say值,于是攻击流量(代理型CC和低级发包型CC)便在302地狱无法自拔了。

防扫描

前人总结的知识结晶已经很完善了,可以直接用loveshell/ngx_lua_waf来做防护。

自定义WAF规则预防常见攻击

防止sql注入

利用WAF防止sql注入可以通过过滤sql注入所需要的关键字,例如union select等来进行:

  1. #==Block SQL Injections
  2. set $block_sql_injections 0;
  3. 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)")
  4. {
  5. set $block_sql_injections 1;
  6. }
  7. if ( $block_sql_injections = 1)
  8. {
  9. return 403;
  10. }

防止xss攻击

通过过滤插入xss标签的常用标点符号进行防护:

  1. set $block_xss 0;
  2. if ($query_string ~ "(~|{|}|"|'|<|>|?)")
  3. {
  4. set $block_xss 1;
  5. }
  6. if ( $block_xss = 1)
  7. {
  8. return 403;
  9. }

防止ssrf

最简单的方法:使用正则表达式过滤属于内网的IP地址

防止rce

借鉴了 常做的ctf题目的思路,还使用了通配符防止简单绕过

  1. set $block_rce 0;
  2. 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")
  3. {
  4. set $block_rce 1;
  5. }
  6. if ( $block_rce = 1)
  7. {
  8. return 403;
  9. }

攻击|各种姿势绕过WAF

绕过WAF主要利用的是三方面的特性 ,服务器特性,应用层特性,WAF层特性

利用服务器特性绕waf

绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图2

%特性(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:

  1. s%u0065lect->select s%u00f0lect->select

WAF层可能能识别s%u0065lect的形式,但是很有可能识别不了s%u00f0lect的形式。这样就可以利用起来做WAF的绕过。

apache畸形method

在GET请求中,GET可以替换为任意参数名字,不影响apahce接收参数id=1:

  1. TEST /sql.php?id=1 HTTP/1.1
  2. Host: 127.0.0.1
  3. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
  4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  5. Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
  6. Accept-Encoding: gzip, deflate
  7. Connection: close
  8. Upgrade-Insecure-Requests: 1

利用应用层特性绕WAF

绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图3

大小写转换

使用简单,但是效果不好。如:
and 1=2 可以使其变为 AnD 1=2
绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图4

*注释

使用简单,效果一般:

  1. union select 1,2,3,4, from admin

注释完的效果

  1. /**/union/**/select/**/1,2,3,4 from admin
  2. /**/un/**/io/**/n/**/sel/**/ec/**/t/**/1,2,3,4 from admin

*!注释

效果显著。此处感叹号为”非”的意思,意思是感叹号后面的不注释:

  1. /*!and*/ 1=2

%url编码
  1. /=%2f*==%2a%=%25
  2. /**/==%252f%252a*/

PS:正常编码为%27
PS:URL编码现在基本过不掉了

使用空字节

一些过滤器在处理输入时,如果碰到空字节就会停止处理
我们通常也会利用空字节进行绕过过滤器。如:

  1. id=1 %00 and 1=2

利用嵌套剥离

有些过滤器会从用户的输入中进行剥离一些敏感的函数。
那我们可以通过函数的嵌套进行绕过一次剥离:selselectect剥离后为select 。

宽字节

union = uю%69яю这里把i不用宽字节 直接url编码 其他的字符都用对应的宽字节
select = こхlх%уt //t不编码 其他的都宽字节 中间插上%
from = цR%яэ //宽字节+%
空格=%20=%ва //в是2的款字符 а是0的宽字符
, = Ь //,号的宽字节

参数污染

通常在一个请求中,同样名称的参数只会出现一次。但是在HTTP协议中是允许同样名称的参数出现多次的。针对同样名称的参数出现多次的情况,不同的服务器的处理方式会不一样:
例如针对sql注入的测试:

  1. $sql = "select * from admin where id=1";
  2. $sql=$sql."".$_POST['bbs'];
  3. echo $sql;
  4. $pattern='/^.*union.*$/';
  5. if(preg_match($pattern, $sql))
  6. {
  7. echo "注入测试";
  8. }

在特定情况下 可以绕过一些WAF

  1. bbs=u&bbs=n&bbs=i&bbs=o&bbs=n&bbs=select 1,user(),3

应用层组合拳绕过waf

替换法+URL编码
例:先判断注入点,把and为&&,urlencode后为%26%26

  1. http://192.168.60.68/sql.php?id=1%20%26%26%20-1=-2

同样的,替换法可以和其他方法组合使用。例如:注释+替换法、内联注释+替换法:

  1. 利用()代替空格
  2. 利用mysql特性/!/执行语句
  3. 利用//混淆代码**

我给出的注入语句是:

  1. union/%00//!50010select/(database//()),(user//())%23 (50010代表数据库版本)

这里要注意的几点是:

  1. mysql关键字中是不能插入//的,即se//lect是会报错的,但是函数名和括号之间是可以加上//的,像database//()这样的代码是可以执行的
  2. /!/中间的代码是可以执行的,其中50010为mysql版本号,只要mysql大于这个版本就会执行里面的代码
  3. 数据或者函数周围可以无限嵌套()
  4. 利用好%00 user())

绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图5

利用WAF层特性绕WAF

绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图6

逻辑问题

  1. 云WAF防护,一般我们会尝试通过查找站点的真实IP,从而绕过CDN防护。
  2. 当提交GET、POST同时请求时,进入POST逻辑,而忽略了GET请求的有害参数输入,可轻易Bypass。
  3. HTTP和HTTPS同时开放服务,没有做HTTP到HTTPS的强制跳转,导致HTTPS有WAF防护,HTTP没有防护,直接访问HTTP站点绕过防护。
  4. 特殊符号%00,部分WAF遇到%00截断,只能获取到前面的参数,无法获取到后面的有害参数输入,从而导致Bypass。

比如:id=1%00and 1=2 union select 1,2,column_name from information_schema.columns

性能问题

第一种情况:在设计WAF系统时,考虑自身性能问题,当数据量达到一定层级,不检测这部分数据。只要不断的填充数 据,当数据达到一定数目之后,恶意代码就不会被检测了:

  1. ?a0=0&a1=1&.....&a100=100&id=1 union select 1,schema_name,3 from
  2. INFORMATION_SCHEMA.schemata

备注:获取请求参数,只获取前100个参数,第101个参数并没有获取到,导致SQL注入绕过。

  1. ?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:
    绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图7
    可以实验一下,如果关闭burp在repeater选项里面自带的content-length补全功能,然后去掉Content-Length,就无法接收到id:
    绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图8
    接下来我们将其改为分块传输的方式:
    绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图9
    通过上图可以看到,即使没有Content-Length,我们也可以采用分块传输的方式,分多少块,每块多大都不是唯一的,但是最后的结尾需要一个长度为0内容为空的块(内容为两个空行)。

    利用分块传输绕过WAF

    当我们将传输的内容分块时,处理后的HTTP请求由于和已知的payload相差较大,所以可以起到一定的绕过WAF的效果。
    比如我们来试试安全狗:
    绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图10
    但是有一些如Imperva的,360等比较好的WAF已经对传输编码的分块传输做了处理,可以把分块组合成完整的HTTP数据包,这时直接使用常规的分块传输方法尝试绕过的话,会被WAF直接识别并阻断。
    这个时候我们可以在每个分块长度标识处加上分号“;”作为注释,如下所示:
    绿冰壶QAQ - WAF攻与防:自定义WAF规则与WAF绕过 - 图11
    在分块数据包中加入注释的话,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系统设计