[转] MySQL 报错注入原理分析(count()、rand()、group by)
原文链接:http://drops.wooyun.org/tips/14312
0x00 疑问
一直在用mysql数据库报错注入方法,但为何会报错?
百度谷歌知乎了一番,发现大家都是把官网的结论发一下截图,然后执行sql语句证明一下结论,但是没有人去深入研究为什么rand不能和order by一起使用,也没彻底说明三者同时使用报错的原理。
0x01 位置问题?
select count(*),(floor(rand(0)*2))x from information_schema.tables group by x;
由上面的图片,可以知道报错跟位置无关。
0x02 绝对报错还是相对报错?
是不是报错语句有了
然后我们新增一条记录。 然后再测试下报错语句 多次执行并没有报错
OK 那我们再增加一条 执行报错语句 ok 成功报错
由此可证明
0x03 随机因子具有决定权么(rand()和rand(0))
为了更彻底的说明报错原因,直接把随机因子去掉,再来一遍看看,先看一条记录的时候,如下图: 一条记录的话 无论执行多少次也不报错
然后增加一条记录。
两条记录的话 结果就变成不确定性了 随机出现报错。
然后再插入一条
三条记录之后,也和2条记录一样进行随机报错。
由此可见报错和随机因子是有关联的,但有什么关联呢,为什么直接使用
0x04 不确定性与确定性
前面说过,
为了证明我们猜想,分别对
mysql> select floor(rand()*2) from `T-Safe`;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 0 |
| 0 |
| 0 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
| 0 |
+-----------------+
12 rows in set (0.00 sec)
mysql> select floor(rand()*2) from `T-Safe`;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 0 |
+-----------------+
12 rows in set (0.00 sec)
mysql> select floor(rand()*2) from `T-Safe`;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 1 |
| 1 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
+-----------------+
12 rows in set (0.00 sec)
连续3次查询,毫无规则,接下来看看select floor(rand(0)*2) from `T-Safe`;
mysql> select floor(rand(0)*2) from `T-Safe`;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
+------------------+
12 rows in set (0.00 sec)
mysql> select floor(rand(0)*2) from `T-Safe`;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
+------------------+
12 rows in set (0.00 sec)
mysql> select floor(rand(0)*2) from `T-Safe`;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
+------------------+
12 rows in set (0.00 sec)
可以看到
0x05 count与group by的虚拟表
使用select count(*) from `T-Safe` group by x;
mysql> select * from Tsafe;
+-----+--------+
| id | name |
+-----+--------+
| 3 | test3 |
| 4 | test4 |
| 5 | test5 |
| 1 | test1 |
| 2 | test2 |
| 6 | test6 |
| 7 | test7 |
| 8 | test8 |
| 9 | test9 |
| 10 | test10 |
| 11 | test11 |
| 12 | test12 |<-----
| 13 | test12 |<-----
| 14 | test12 |<-----
| 15 | test12 |<-----
| 16 | test12 |<-----
+-----+--------+
16 rows in set (0.00 sec)
mysql> select name,count(*) from Tsafe group by name;
+--------+----------+
| name | count(*) |
+--------+----------+
| test1 | 1 |
| test10 | 1 |
| test11 | 1 |
| test12 | 5 |<-----
| test2 | 1 |
| test3 | 1 |
| test4 | 1 |
| test5 | 1 |
| test6 | 1 |
| test7 | 1 |
| test8 | 1 |
| test9 | 1 |
+--------+----------+
12 rows in set (0.00 sec)
可以看出 test12的记录有5条
与select count(*) from TSafe group by x;
-
先建立虚拟表,如下图(其中key是主键,不可重复):
-
开始查询数据,取数据库数据,然后查看虚拟表存在不,不存在则插入新记录,存在则count(*)字段直接加1,如下图:
由此看到 如果key存在的话就+1, 不存在的话就新建一个key。
那这个和报错有啥内在联系,我们直接往下来,其实到这里,结合前面的内容大家也能猜个一二了。
0x06 floor(rand(0)*2)报错
其实mysql官方有给过提示,就是查询的时候如果使用select count(*) from TSafe group by floor(rand(0)*2);
-
查询前默认会建立空虚拟表如下图:
-
取第一条记录,执行
floor(rand(0)*2) ,发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在,则floor(rand(0)*2) 会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录查询完毕,如下图: -
查询第二条记录,再次计算
floor(rand(0)*2) ,发现结果为1(第三次计算),查询虚表,发现1的键值存在,所以 不会被计算第二次,直接floor(rand(0)*2)
加1,第二条记录查询完毕,结果如下:count(*)
-
查询第三条记录,再次计算
floor(rand(0)*2) ,发现结果为0(第4次计算),查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2) 被再次计算,作为虚表的主键,其值为1(第5次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(主键键值必须唯一),所以插入的时候就直接报错了。 -
整个查询过程
floor(rand(0)*2) 被计算了5次,查询原数据表3次,所以这就是为什么数据表中需要3条数据,使用该语句才会报错的原因。
0x07 floor(rand()*2)报错
由0x05我们可以同样推理出不加入随机因子的情况,由于没加入随机因子,所以
最重要的是前面几条记录查询后不能让虚表存在0,1键值,如果存在了,那无论多少条记录,也都没办法报错,因为
当前面记录让虚表长成这样子后,由于不管查询多少条记录,floor(rand()*2)
count(*)
mysql> select floor(rand(1)*2) from mysql.user;
+------------------+
| floor(rand(1)*2) |
+------------------+
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
+------------------+
7 rows in set (0.00 sec)
mysql> select floor(rand(1)*2) from mysql.user;
+------------------+
| floor(rand(1)*2) |
+------------------+
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
+------------------+
7 rows in set (0.00 sec)
mysql> select floor(rand(1)*2) from mysql.user;
+------------------+
| floor(rand(1)*2) |
+------------------+
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
+------------------+
7 rows in set (0.00 sec)
在前两条记录查询后,虚拟表已经存在0和1两个键值了,所以后面再怎么弄还是不会报错。
总之报错需要