Java 升级之路(三)Java 反序列化漏洞


Java 升级之路(三)Java 反序列化漏洞反序列化漏洞Java反序列化漏洞之readObject()Java反序列化漏洞分析 — Commons Collections

反序列化漏洞

还是先拿php来说,其反序列化漏洞是攻击者通过构造特定的php序列化字符串,在unserialize()时,依靠所构造的类中的__wakeup()__construct()__destruct()等魔术方法或复杂的利用链从而最终达到某种攻击。

Demo:

漏洞的关键就是用户可以自定义序列化内容给程序去执行反序列化操作

 

Java反序列化漏洞之readObject()

通过之前对Java序列化和反序列化的学习,再从php类比到Java,如果某Java程序准许反序列化用户提供的内容时,也可能导致相应的漏洞。

上一篇中也提到过用readObject()来实现java的反序列化,Java反序列化中readObject()的作用其实就相当于PHP反序列化中的那些魔术方法。可以修改正常Java程序生成的序列化内容,从而在反序列化时控制字段等操作。

其实readObject()也是一个在类中可以自定义的方法,在Java反序列化时,会直接调用被反序列化类的readObject()方法,如果当readObject()方法书写不当时就会引发漏洞。

重写readObject() Demo:

输出readObject~,其中的s.defaultReadObject();是执行默认的readObject()方法

Java反序列化中readObject()的作用其实就相当于PHP反序列化中的那些魔术方法。这里的代码当然没有什么问题,但如果目标对象的readObject()有一些危险操作或者进行了一些更复杂的流程,有可能会带来安全问题。

假设有个存在漏洞的代码,如下:

那么可以利用如下代码,构造序列化数据

再用第一个去反序列化这段数据,可以在本地2333端口监听到curl请求

PS. 有时也会使用readUnshared()方法来读取对象,readUnshared()不允许后续的readObject()readUnshared()调用引用这次调用反序列化得到的对象,而readObject()读取的对象可以。

 

Java反序列化漏洞分析 — Commons Collections

当服务端允许接收远端数据进行反序列化时,客户端可以提供任意一个服务端存在的对象 (包括依赖包中的对象) 的序列化二进制串,由服务端反序列化成相应对象。如果该对象是由攻击者『精心构造』的恶意对象,而它自定义的readObject()中存在着一些『不安全』的逻辑,那么在对它反序列化时就有可能出现安全问题。

真实存在的漏洞当然没有上述Demo这么简单,下面分析分析实际存在的Java反序列化漏洞

Apache Commons Collections来看

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。

简单来说,这个漏洞的简单原理就是:

AnnotationInvocationHandler类的readObject()方法中对Map类型的变量进行了键值修改操作,并且这个Map变量是外部(序列化数据)可控的

而在Commons Collections中,一个精心构造的TransformedMap,在其任意键值被修改时,可以触发一系列变换,最终达到任意命令执行

Commons Collections中实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,TransformerTransformedMap实例化时作为参数传入。我们可以通过TransformedMap.decorate()方法,获得一个TransformedMap的实例

TransformedMap内的key或者value发生变化时,就会触发相应的Transformertransform()方法。

另外,还可以使用Transformer数组构造成ChainedTransformer。当触发时,ChainedTransformer可以按顺序调用一系列的变换。而Apache Commons Collections已经内置了一些常用的Transformer,其中InvokerTransformer类就是这个漏洞所用到的

org/apache/commons/collections/functors/InvokerTransformer.java

其构造方法和transform()方法如下:

这个transform(Object input)中使用反射机制调用了input对象中的一个方法,而其中的几个参数iMethodName, iParamTypes,iArgs都是实例化InvokerTransformer类时传入的methodName, pamamTypes, args

也就是说这段反射代码中的调用的方法名和Class对象均可控。于是,我们可以构造一个恶意的Transformer链,借用InvokerTransformer.transform()执行任意命令。

首先漏洞测试代码如下:

执行即可在2333端口收到curl请求

接着来一步步分析:

首先看到

我们先定义了trans,而ChainedTransformertransform()时,会将前一个元素的返回结果作为下一个的参数

于是看到trans内部

ConstantTransformer,顾名思义可以将待变换的对象,变为一个常量,它的transform()方法代码如下:

于是在接下来的transform()时,会返回Runtime.class这个对象

 

根据InvokerTransformer的构造方法可知,此句构造了一个InvokerTransformer,其待调用方法名为getMethod,参数为getRuntime

transform()时,传入上面得到的常量Runtime.class,此时的input应该是java.lang.Runtime

但经过getClass()后,clsjava.lang.Class,之后的getMethod()只能获取java.lang.Class的方法

因此才会定义的待调用方法名为getMethod,然后其参数才是getRuntime,它得到的是getMethod这个方法的Method对象,invoke()调用这个方法

最终得到的才是getRuntime这个方法的Method对象,并转为常量

构造一个InvokerTransformer,待调用方法名为invoke,参数为空,在transform()时,传入上面得到的getRuntime,同理,cls将会是java.lang.reflect.Method,再获取并调用它的invoke方法,实际上是调用上面的getRuntime()拿到Runtime对象

 

构造一个InvokerTransformer,待调用方法名为exec,参数为命令字符串,在transform()时,传入上面得到的Runtime,获取java.lang.Runtimeexec方法并传参调用cmd

这样,这段代码本质上就是利用反射调用Runtime执行了一段系统命令,作用等同于构造了:

接下来是如何触发的问题

首先定义一个普通的HashMap:normalMapput()一组数据

然后调用TransformedMap.decorate()获得一个TransformedMap的实例

使用方法iterator()要求容器返回一个Iterator。第一次调用Iteratornext()方法时,它返回序列的第一个元素。

setValue()时改变了键值

如果这时输出transformedMap的话会得到{key=java.lang.UNIXProcess@58372a00}

 

到此弄懂了RCE链,那么如何利用Java反序列化触发呢?

现在,我们只需要再找一个包含可控Map字段,并会在反序列化时对这个Map进行setValue()get()操作的公共对象

在JDK较早的版本中可用sun.reflect.annotation.AnnotationInvocationHandler这个对象(较新版本的JDK可以使用BadAttributeValueExpException

它的成员变量memberValueMap<String, Object>类型,并且在重写的readObject()方法中有memberValue.setValue()的操作。

这样提前构造好的数据,在调用AnnotationInvocationHandler的反序列化时,触发漏洞

 

(网上Copy了一份脚本: