前言
什么是序列化和反序列化
Java 提供了一种对象序列化的机制, 该机制中, 一个对象可以被表示为一个字节序列, 该字节序列包括该对象的数据, 有关对象的类型的信息和存储在对象中数据的类型. 反序列化就是通过序列化后的字段还原成这个对象本身. 但标识不被序列化的字段是不会被还原的.
序列化有什么用
1) 网站相应的 session 对象存储在硬盘上, 那么保存在 session 中的内容就必须实现相关的序列化操作.
2) 如果使用的 java 对象要在分布式中使用或者在 rmi 远程调用的网络中使用的话, 那么相关的对象必须实现 java 序列化接口.
Java 反序列化类型
我们最常见就是原生的 java 反序列化类型, 其实 java 中有几种方式可以执行反序列化, 本文目的也是对这几种类型的反序列化方法进行归纳和总结.
1, Java 原生序列化
Java 包中自带的类 InputStream 和 OutputStream, 它们之间可以互相转化, 使用 writeObject 序列化, 使用 readObject 反序列化.
- import java.io.*;
- public class DeserializeDemo
- {
- public static void main(String [] args)
- {
- Employee e = null;
- try
- {
- FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
- ObjectInputStream in = new ObjectInputStream(fileIn);
- e = (Employee) in.readObject();
- in.close();
- fileIn.close();
- }catch(IOException i)
- {
- i.printStackTrace();
- return;
- }catch(ClassNotFoundException c)
- {
- System.out.println("Employee class not found");
- c.printStackTrace();
- return;
- }
- System.out.println("Deserialized Employee...");
- System.out.println("Name:" + e.name);
- System.out.println("Address:" + e.address);
- System.out.println("SSN:" + e.SSN);
- System.out.println("Number:" + e.number);
- }
- }
2, JSON 反序列化
JSON 序列化一般会使用 jackson 包, 通过 ObjectMapper 类来进行一些操作, 比如将对象转化为 byte 数组或者将 JSON 串转化为对象.
- public static <T> String serialize(T t) throws JsonProcessingException {
- ObjectMapper mapper = new ObjectMapper();
- String jsonResult = mapper.writerWithDefaultPrettyPrinter()
- .writeValueAsString(t);
- return jsonResult;
- }
3, Fastjson 反序列化
Fastjson 是一个性能很好的 Java 语言实现的 JSON 解析器和生成器, 由来自阿里巴巴的工程师开发. 具有极快的性能, 超越任何其他的 Java JSON Parser.Fastjson 使用 parseObject 来进行反序列化.
- import com.alibaba.fastjson.JSON;
- public class Person {
- int age;
- String name;
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public static void main(String[] args) {
- String jsonString="{\"name\":\"hah\",\"age\":1}";
- Person person = JSON.parseObject(jsonString, Person.class);
- System.out.println(1);
- }
- }
4,Protobuf 反序列化
Protocol Buffers 是一种轻便高效的结构化数据存储格式, 可以用于结构化数据串行化, 或者说序列化. 它很适合做数据存储或 RPC 数据交换格式. 可用于通讯协议, 数据存储等领域的语言无关, 平台无关, 可扩展的序列化结构数据格式. 目前提供了 C++,Java,Python 三种语言的 API.
proto.proto 文件内容
- package proto;
- message TestMsg{
- optional string id = 1;
- optional string name = 2;
- }
序列化
- public byte[] build(){
- Proto.TestMsg.Builder builder = Proto.TestMsg.newBuilder();
- builder.setId("ID 的值");
- builder.setName("Name 的值");
- Proto.TestMsg msg = builder.build();
- return msg.toByteArray();
- }
反序列化
- Proto.TestMsg msg = Proto.TestMsg.parseFrom(message.returnByte());
- System.out.Println(msg);
各方式反序列化比较
各序列化漏洞简介
除了使用 protobuf 进行反序列化没有出现过漏洞, 其他方式的序列化都曾出现过漏洞. 下面将简单介绍下漏洞, 详细的漏洞和 exp 构造方法大家可以去网上搜索关键字查看 (java 几个反序列化漏洞 exp 构造过程都十分精彩, 推荐大家认真阅读下)
1,Object Serialize 漏洞
Apache Commons Collections 中实现了 TransformedMap , 该类可以在一个元素被添加 / 删除 / 或是被修改时 (即 key 或 value: 集合中的数据存储形式即是一个索引对应一个值, 就像身份证与人的关系那样), 会调用 transform 方法自动进行特定的修饰变换.
TransformedMap.decorate 方法, 预期是对 Map 类的数据结构进行转化, 该方法有三个参数.
第一个参数为待转化的 Map 对象
第二个参数为 Map 对象内的 key 要经过的转化方法 (可为单个方法, 也可为链, 也可为空)
第三个参数为 Map 对象内的 value 要经过的转化方法
通过对第三个参数通过构造 ChainedTransformer 链, 通过一系列变化, 最终执行系统命令.
2,Jackson-databind 漏洞
Jackson 是一套开源的 java 序列化与反序列化工具框架, 可将 java 对象序列化为 xml 和 JSON 格式的字符串及提供对应的反序列化过程. 由于其解析效率较高, 目前是 Spring MVC 中内置使用的解析方式, 该漏洞的触发条件是 ObjectMapper 反序列化前调用了 enableDefaultTyping 方法. 该方法允许 JSON 字符串中指定反序列化 java 对象的类名, 而在使用 Object,Map,List 等对象时, 可诱发反序列化漏洞, 导致可执行任意命令.
3,FastJson 漏洞
fastjson 在解析 JSON 的过程中, 支持使用 autoType 来实例化某一个具体的类, 并通过 JSON 来填充其属性值. 而 JDK 自带的类 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 中有一个私有属性_bytecodes, 其部分方法会执行这个值中包含的 Java 字节码. 通过注入恶意代码到_bytecode, 导致任意代码执行漏洞.
注: Fastjson 和 Jackson Payload 构造的方式都一样, 虽然解析函数不一样, 但是都是将 JSON 转为 object, 过程是类似的.
防止反序列化漏洞
1,Java Serialization
jdk 里增加了一个 filter 机制 http://openjdk.java.net/jeps/290 http://openjdk.java.net/jeps/290 , 这个一开始是出现在 jdk9 上的, 后面移值回 jdk6/7/8 上, 如果安装的 jdk 版本是比较新的, 可以找到相关的类
Oracle 打算废除 java 序列化:
2,jackson-databind
jackson-databind 里是过滤掉一些已知的类, 参见
jackson-databind 的 CVE issue 列表
3,fastjson
fastjson 通过一个 denyList 来过滤掉一些危险类的 package, 参见
fastjson 在新版本里 denyList 改为通过 hashcode 来隐藏掉 package 信息, 但通过这个可以知道还是过滤掉常见危险类的 package
fastjson 在新版本里默认把 autoType 的功能禁止掉了
这些序列化漏洞的根本原因是: 没有控制序列化的类型范围.
仔细看的读者会发现并没有提及 protobuf 的反序列化漏洞, 为什么在 protobuf 里并没有这些反序列化问题?
protobuf 在 IDL 里定义好了 package 范围
protobuf 的代码都是自动生成的, 怎么处理二进制数据都是固定的
protobuf 把一切都框住了, 少了灵活性, 自然就少漏洞.
注: IDL(Interface description language) 文件: 参与通讯的各方需要对通讯的内容需要做相关的约定 (Specifications). 为了建立一个与语言和平台无关的约定, 这个约定需要采用与具体开发语言, 平台无关的语言来进行描述. 这种语言被称为接口描述语言 (IDL), 采用 IDL 撰写的协议约定称之为 IDL 文件.
总结:
本文总结了 java 反序列化的几种方式, 并回顾了 java 几个经典的漏洞以及对应的修复方案, 希望通过本文, 大家对 java 反序列化漏洞有更深刻的认知.
参考链接:
- https://www.freebuf.com/sectool/165655.html
- https://www.cnblogs.com/he1m4n6a/p/10131566.html
- https://www.jianshu.com/p/e9e631285cb0
来源: https://www.cnblogs.com/he1m4n6a/p/10270696.html