java反序列化漏洞原理分析

0x00 反序列化原理

1.从java序列化机制谈起

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
类ObjectInputStream 和ObjectOutputStream是高层次的数据流,它们包含序列化和反序列化对象的方法。

java序列化是将我们的java对象封装成静态二进制格式,这样就适合存储在磁盘上或者通过网络传输。这里需要清楚知道类和对象的概念。对象是通过类生成的一个实例。
java反序列化与序列化过程正好相反,通过读取序列化后的静态二进制格式数据。然后,通过已有类将数据解析出来,重新生成这个对象,并进行使用。这样就完成了数据的解析过程。

代码运行结果输出:bob。
上述代码将字符串对象String name进行了序列化处理,并存储在本地的name.ser文件中。然后,通过读取文件,重新生成了String nameFromDisk这个String对象。而里面存储的便是原始name对象数据"bob"。这样就完成了一次通过序列化完成一次数据传输。
我们通过xxd命令查看序列化数据:

可以注意到,序列化数据都是由aced 0005这样的16进制开头。用这个"幻数"就可以判断数据是否为java序列化数据。
而这种数据传输方式,可以用于网络通信中,我们可以将name.ser中的内容使用TCP/IP协议的方式进行传输,进行远程通信。这种机制广泛应用在java程序中。

Java LOVES sending serialized objects all over the place. For example:
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

2. 反序列化过程自动调用类的readObject函数

以下是一个复杂一点的序列化程序,这里有两个类:
PersonEntity.java,个人实体类:存储一个用户的id,名字和年龄信息。为了能够序列化,我们需要继承Serializable接口。

然后是一个序列化程序测试代码:

这里同样将PersonEntity类生成的person1对象进行了序列化操作到文件中,然后读取生成对象。
运行结果为:ID:0 Age:25 Name:Leslie! readObject Method~
看到name值后面加上了readObject Method~。由此可见,java在执行反序列化过程时,即:执行ois.readObject()方法时,便会优先执行对应序列化类(这里是PersonEntity)中的readObject方法。

0x01 反序列化机制攻击利用

通过以上对java序列化机制的简要分析,可以明确几点:

  • 在读取java反序列化的数据中,没有对数据进行过滤机制。我们可以向这个序列化接口传输任意构造的对象数据。
  • 在构造对象时,必须是目标主机上运行环境中存在的类。这样才能被目标主机解析并生成新的对象。
  • 目标主机类中,如果有readObject方法。我们构造这个类的对象,在解析过程中会优先执行这个类的readObject方法。

在java反序列化这种机制中,可以看到似乎有问题。但是缺少一个利用方法,去爆发这个安全问题,这时第三方类的帮忙,使漏洞一下子爆发了。

1. Apache Commons Collections库

Apache Commons Collections库是Apache公司开发的公共工具库中的一个,里面封装了一些java集合框架操作,实现了对Map,Set,List等数据集合的扩展。大量地应用在java程序开发过程中。

Map类是存储键值对的数据结构。Apache Commons Collections中实现了类TransformedMap,用来对Map进行某种变换。只要调用decorate()函数,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMap,decorate()函数如下:

  • 其中,Transformer参数 是定义的如何变换的函数
  • Map中的任意项的Key或者Value被修改,相应的Transformer(keyTransformer或者valueTransformer)的transform方法就会被调用。

Transformer接口为:

TransformedMap实现了Map接口,并且其中有个checkSetValue方法,每当调用map的setValue方法时,该方法将会被调用。

TransformedMap父类AbstractInputCheckedMapDecorator类中有对setValue的重写:

然后,就是调用transformer方法。 流程即为:setValue ==> checkSetValue ==> valueTransformer.transform(value)

2. 构建ChainedTransformer链执行任意代码

(1) 寻找敏感的transformer。比如,反射机制调用方法等操作。

Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer,代码如下:

这里我们需要传入三个参数:方法名,参数类型,参数值。这样就可以调用对象中的任意函数了。但这里的Class cls = input.getClass();,我们还没有控制input这个Object类型对象。

(2) ChainedTransformer 形成最终执行代码链

现在解决无法控制对象的问题,发现了ChainedTransformer这个类。
多个Transformer还能串起来,可以形成一个ChainedTransformer,它也继承了Transformer接口。具体代码如下:

这个函数迭代了所有Transformer类中transform方法,并将传回来的object对象放到下一个Transformer中进行使用。
这样就好办了,因为这里还有一个ConstantTransformer类,它只是返回我们传进去的对象。于是我们可以传入Runtime.class对象,就能进行命令执行操作了。代码如下:

这样就可以形成我们一个执行任意代码的执行链了:

然后通过上述分析的触发条件,生成一个TransformedMap,并且修改这个Map的value值,即可触发。代码测试如下:

运行并弹出计算器:

3. 使用AnnotationInvocationHandler类自动触发

以上代码需要自己通过setValue方法,触发漏洞发生。现在需要解决如何在序列化数据反序列化时,执行readObject()方法,并自动触发。
恰好,在搜索readObject方法时,发现AnnotationInvocationHandler
该类是java运行库中的一个类,并且包含一个Map对象属性,其readObject方法有自动修改自身Map属性的操作。代码如下:

这样我们就可以将TransformedMap当做参数,传入memberValues值中。当java每次读取序列化对象是,就会触发命令执行的Transform链。生成payload并测试代码如下:

生成payload的类PayloadGeneration

测试exploit:

运行结果:

可以看看生成的payLoad数据:

序列化数据依旧是由幻数,"aced 0005"开头。

做个小的总结:

  • 反序列化只能使用服务器环境类。
  • 只能对属性进行修改赋值,通过这样的修改属性,去触发代码执行等。
  • java默认在反序列化过程中会调用类的readObject方法,通过寻找类的readObject方法,查看是否有敏感操作利用的空间。最后通过修改属性值,去执行任意代码等操作。

4. 国外使用LazyMap的触发用法

(1) 动态代理技术

代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。代理的存在对于调用者来说是透明的,调用者看到的只是接口。
JDK 5引入的动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。

这是涉及一个代理模式,请参考这个例子:http://xiaoniudu2008.iteye.com/blog/236050

首先,看看java动态代理技术,简单地看看一下代码:

这是一个实现了InvocationHandler接口的代理类:

实现功能为:代理了一个List对象。并且当被代理类调用size方法时,就抛出UnsupportedOperationException异常。
测试代码为:

测试代码运行结果为:

通过上面实例可以看出,使用:

可以代理一个List接口,使其所有的方法操作,都会经过listProxyinvoke方法进行操作。

(2) LazyMap类

LazyMap类和TransformedMap类一样,有个decorate方法,可以传入上述的transformer变换类。

并且存在get方法去触发transform操作:

幸运的是,在AnnotationInvocationHandler类的invoke方法中,发现了调用memberValues.get(Object)方法。

(3)动态代理触发

AnnotationInvocationHandler类实现了InvocationHandler接口,所以它可以代理其他对象。所以,这里利用这个特点,代理一个Map对象,得到mapProxy代理对象。然后,将mapProxy赋值到AnnotationInvocationHandler类中,当其调用mapProxy方法时,便可以触发invoke()方法。然后,便可以执行transformed链。

同样使用ransformerChain执行任意代码,POC如下:

  • 注意上述代码两次生成AnnotationInvocationHandler对象,功能不同。
  • 代码的执行流程为:

测试代码和结果为:

0x02 php反序列化对比

之前我重现了下php论坛框架Joomla反序列化导致的代码执行漏洞,现在将里面的POC进行分析。

对于php来说,首先要找到反序列化数据的处理位置,Joomla这个漏洞已经找到。接下来是构造利用。

  • php中存在__destruct析构函数,即:在对象销毁时,会自动执行里面的代码。与java序列化中readObject方法类似。
  • 同样,必须在目标主机中找到可以利用类,里面包含敏感操作。这里更加直接,析构函数中直接包含敏感操作。没有像java反序列化中,通过寻找修改map值的readObject函数,来触发transform调用。

在Joomla3中,成功在析构函数(__destruct)中找到了敏感操作call_user_func_array,查询这个函数可知:

该函数具有调用任意函数的功能,比如:

这里就会执行assert(phpinfo())代码。
JDatabaseDriverMysqli类中存在该类敏感操作,但是控制不了第二个参数,如下:

但是,可以发现有这个用法:

以上即为调用$foo->bar("three", "four")
于是,再次找到一个敏感操作地方,simplepie类中init函数存在敏感操作:

因此,这里新建一个simplepie类,并且将其中cache_name_functionfeed_url属性赋值为:assertphpinfo,便可执行phpinfo这个代码。
于是有了POC:

整体的执行代码流程为:

做个小总结:

  • php序列化也需要寻找自动调用的函数__destruct,查看里面是否存在敏感操作。
  • 只能利用目标服务器上已存在的类。
  • 只能通过对对象的赋值操作,进行漏洞利用。

0x03 参考及代码

http://blog.chaitin.com/2015-11-11_java_unserialize_rce/
http://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/
http://drops.wooyun.org/papers/11330

本文所示代码: https://github.com/angelwhu/java_unserialize