Java反序列化漏洞的原理分析

作者:媒体转发 时间:2018-05-06 16:37

字号

世界上有三件事最难:

把别人的钱装进自己的口袋里

把自己的想法装进别人的脑袋里

让自己的代码运行在别人的服务器上

前言

Java反序列化漏洞是近一段时间里一直被重点关注的漏洞,自从 Apache Commons-collections 爆出第一个漏洞开始,围绕着Java反序列化漏洞的事件就层出不穷,为了详细了解Java反序列化漏洞的成因和原理,本文将以 ysoserial 项目作为基础,以普通Java工程师的角度来逐步解释这类漏洞的原理。

本文涉及了大量的源码,尽可能保证开发者能够快速搭建实验环境进行漏洞的复现。Java反序列漏洞涉及大量的Java基础,而漏洞利用过程复杂巧妙,为了清晰地表达出其中的原理,粘贴了大量的代码片段。

核心要点

Java反序列化与 ObjectInputStream

在Java中,利用ObjectInputStream的readObject方法进行对象读取时,当目标对象已经重写了readObject方法,则目标对象readObject方法。如下代码所示

public class ReadObject implements Serializable { private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException{ System.out.println("read object in ReadObject"); } public static void main(String[] args) throws IOException, ClassNotFoundException { byte[] serializeData=serialize(new ReadObject()); deserialize(serializeData); } public static byte[] serialize(final Object obj) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(out); objOut.writeObject(obj); return out.toByteArray(); } public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { ByteArrayInputStream in = new ByteArrayInputStream(serialized); ObjectInputStream objIn = new ObjectInputStream(in); return objIn.readObject(); } }

以上代码将会输出

read object in ReadObject

可见在反序列化的过程中,如果目标对象的readObject进行了一些更复杂的操作的时候,那么极有可能给恶意代码提供可乘之机。

利用java的反射来执行代码

Java的反射机制提供为Java工程师的开发提供了相当多的便利性,同样也带来了潜在的安全风险。反射机制的存在使得我们可以越过Java本身的静态检查和类型约束,在运行期直接访问和修改目标对象的属性和状态。Java反射的四大核心是 Class,Constructor,Field,Method,如下代码所示。我们将利用Java的反射机制来操纵代码调用本地的计算器

public static void main(String[] args) throws Exception { Object runtime=Class.forName("java.lang.Runtime") .getMethod("getRuntime",new Class[]{}) .invoke(null); Class.forName("java.lang.Runtime") .getMethod("exec", String.class) .invoke(runtime,"calc.exe"); }

以上代码中,我们利用了Java的反射机制把我们的代码意图都利用字符串的形式进行体现,使得原本应该是字符串的属性,变成了代码执行的逻辑,而这个机制也为我们后续的漏洞使用的前提。

从零开始

为了尽可能地将Java反序列化漏洞的原理讲述清楚,在本章节中,我们将站在一个攻击者和漏洞利用者的角度去观察如何使用Java的反序列化漏洞。

环境

要完成实验需要添加如下版本的库

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency> <!-- 用于修改字节码--> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.22.0-GA</version> </dependency>

靶子

在进行攻击之前,我们需要模拟出一个靶子,靶子代码如下,其主要功能是监听本地端口,并将端口中的数据进行反序列化。

public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(10000); while (true) { Socket socket = server.accept(); execute(socket); } } public static void execute(final Socket socket){ new Thread(new Runnable() { public void run() { try { ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(socket.getInputStream())); Object obj = is.readObject(); } catch (Exception e) { e.printStackTrace(); } } }).start(); }

然而为了更加容易的测试,我们可以将上述的过程描绘为

构造一个恶意的Java对象将这个对象序列化到一个 byte数组从这个byte数组利用反序列化还原对象如果在反序列化的过程中执行了恶意对象的代码,视为漏洞利用成功

因此我们可以将测试的代码简化如下

public static void main(String[] args) throws Exception { deserialize(serialize(getObject())); } //在此方法中返回恶意对象 public static Object getObject(){ return ""; } public static byte[] serialize(final Object obj) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(out); objOut.writeObject(obj); return out.toByteArray(); } public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { ByteArrayInputStream in = new ByteArrayInputStream(serialized); ObjectInputStream objIn = new ObjectInputStream(in); return objIn.readObject(); }

恶意代码

责任编辑:CQITer新闻报料:400-888-8888   本站原创,未经授权不得转载
继续阅读
热新闻
推荐
关于我们联系我们免责声明隐私政策 友情链接