# 简介

PS:跟着大佬学习的,大佬讲得是针不错。https://www.bilibili.com/video/BV16h411z7o9?p=1

Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存或文件中,实现跨平台通讯和持久化存储。ObjectOutputStream 类的 writeObject () 方法可以实现序列化。反序列化则指把字节序列恢复为 Java 对象的过程,相应的,ObjectInputStream 类的 readObject () 方法用于反序列化。

使用场景

  • 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候。
  • 当你想用套接字在网络上传送对象的时候。
  • 当你想通过 RMI 传输对象的时候。

RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个 Java 虚拟机的对象调用运行在另一个 Java 虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。

# java 序列化和反序列化的实现

# JDK 类库中序列化和反序列化 API

JDK 类库中存在序列化和反序列化(原生反序列化)的 api,同时 fastjson、jackson 等等第三方组件也提供的反序列化的 api。这里我们只讨论原生的反序列化。

java.io.ObjectOutputStream:表示对象输出流;它的 writeObject (Object obj) 方法可以对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中;

java.io.ObjectInputStream:表示对象输入流;它的 readObject () 方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;

# 实现序列化的要求

序列化的目标是一个对象,而只有实现了 Serializable(空接口)或 Externalizable 接口的类的对象才能被序列化,否则序列化会失败。
如图,我们这个 Student 对象实现了 Serializable 接口,后续实例化这个对象时,就能够对其进行序列化和反序列化了。

PS:Java 的序列化会将一个类包含的引用中所有的成员变量保存下来(深度复制),所以里面的引用类型必须也要实现 java.io.Serializable 接口。

image-20220918151840325

# 序列化和反序列化实例

首先我们这里把序列化和反序列化的代码补完。
可以看到,我们定义了两个方法,
第一个 Ser 方法接受一个对象,然后把这个对象序列化存储到 student.ser 文件中。
第二个 UnSer 方法则是把指定文件的内容进行反序列化,还原对象,并返回。

image-20220918151859743

这里我们就针对 student 这个对象进行了序列化和反序列化。

image-20220918151910709

image-20220918151917526

# readObject 和 writeObject 方法(重点)

PS: 需要搞清楚序列化能存储的东西!!序列化的目标是对象,而一个对象在实例化后只有成员变量会被存储。也就是说和 php 一样,方法是不会存储的。

我们前面调用的 readObject 和 writeObject 方法都是 ObjectInputStream 它们的方法。

但实际上针对不同的对象,我们可能会想要不同的序列化和反序列化策略。如果都用这个 ObjectInputStream 里面的方法

明显是做不到多样化策略的。所以我们可以在目标对象中重写这两个方法,实现我们想要的策略。

比如上面,我们在 Student 这个类中重写 readObject 这个方法,那么当这个对象被反序列化的时候,就会执行相应的策略。

image-20220918152012864

如图,当我们执行反序列化的时候,因为 Student 这个对象重写了 readObject 方法,就会调用这个方法进行对象的操作。

image-20220918152024348

# 反序列化漏洞

只要服务端反序列化数据,那么客户端传递进来的类的 readObject 中的代码就会自动执行,从而给予攻击者在服务器上运行代码的能力。

这句话怎么理解呢?很简单,以上面为例,当我们传入的对象是 Student 这个对象时,服务器发现本地存在 Student 这个类,然后又发现这个类重写了

readObject,因此就会执行 Student 这个类的 readObject 里的代码。

也就是说当目标服务器接受我们的一个对象,并且把这个对象给反序列化时,如果这个对象的类在目标服务器也存在。并且

重写了 readObject,而恰好的是这个 readObject 中又存在危险命令执行函数的时候,就存在 RCE 了。但显然,这么多条件属实苛刻,现实中基本不可能存在这样的情况,

即使存在,也是开发人员写的测试类,这个类我们也很难得知。自然也无法利用了。

现实中我们的反序列化漏洞更多是以各种第三方组件来实现的,也就是说一些第三方组件中的类在反序列化的时候会存在 RCE 的情况。

这里我们暂时不需要想太多,等到后续我们分析一个链的时,大概就很清晰了。

反序列化漏洞可能存在的情况:

PS:入口类 == 我们可控的输入的类,比如在上面中,我们的入口类就是 Student 这个类。

  • 入口类的 readObject 直接调用危险方法。(前面的例子就是这样,但现实基本不存在)
  • 入口类参数中包含可控类,这个类有危险方法,入口类在 readObject 时触发这个类的危险方法。(分析 URL DNS 链就懂了)
  • 入口类参数中包含可控类,这个类又调用其他有危险方法的类,入口类在 readObject 时调用。(其实就是利用链的问题了,没啥好说的)

# 自建 Servlet 模拟反序列化漏洞的利用过程

# Servlet 和存在漏洞的类的搭建

这里为了更清晰反序列化漏洞的原理,我们自己搭建一个存在漏洞的 Servlet,然后把恶意对象传给它触发漏洞。

image-20220919175256364

然后创建一个测试类,这个测试类是在 Servlet 上的,存在一个危险函数。

image-20220919175305474

# Payload 构造和 EXP

很明显,上面 Servlet 接收请求体,然后把请求体给反序列化。而目标服务器上存在一个存在漏洞的类 User,也就是说我们只要把 User 这个类放到请求体
传给 Servlet,就能进行 RCE 了。

image-20220919175330907

image-20220919175335972

但此时执行 payload 发现报错。

image-20220919175354609

百度一哈,serialVersionUID 参数主要是用来验证 Java 序列化版本一致性的。因此问题就是序列化的版本问题,这里解决方法就是在本地序列化恶意

对象时指定这个 UID 的值,让它和服务端一致就行。

image-20220919175403901

最后执行 EXP,成功弹 Calc

image-20220919175416255