[java安全]-序列化反序列化
Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久化的目的。
java序列化: 对象 Java Object
--> 二进制字节 byte[]
Java反序列化: 把一个二进制字节序列 byte[]
--> Java对象 Java Object
序列化
ObjectOutputStream
字节输出流对象,将对象的输出流写到文件中(结合 FileOutputStream
使用)
Demo:
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
或
FileOutputStream fout = new FileOutputStream("1.txt");
ObjectOutputStream out = new ObjectOutputStream(fout);
此外ObjectOutputStream
还提供了writeObject()
方法来序列化一个对象,并将它发送到输出流。
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(new Demo1("Muz1",20));
序列化操作
一个对象要想序列化,必须满足两个条件:
-
该类必须实现
java.io.Serializable
接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出
NotSerializableException
。 -
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient 关键字修饰。
实例:
Demo1.java
package com.seralize;
public class Demo1 implements java.io.Serializable {
public String name;
public String address;
public transient int age;
public void sout(){
System.out.println("Sout :"+name+"--"+address);
}
public Demo1() {
this.name = name;
this.address = address;
this.age = age;
}
@Override
public String toString() {
return "Demo1{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
'}';
}
}
Serializable.java
package com.seralize;
import com.Muz1.Method.demo1;
import java.io.*;
public class Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Demo1 demo1 = new Demo1();
demo1.name = "Muz1";
demo1.age = 20;
demo1.address = "山东济南";
//1.创建序列化流
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test1.txt"));
//2.写出对象
out.writeObject(demo1);
//3.释放资源
out.close();
}
}
然后将Demo1对象写入到了test1.txt中
开头的AC ED 00 05
为序列化内容的特征
反序列化
ObjectInputStream
字节输入流对象,将文件中的二进制字节序列进行反序列化操作(结合FileInputStream)
Demo:
ObjectInputStream in = new ObjectInputStream(new FileInputStream("1.txt"));
或
FileInputStream fin = new FileInputStream("1.txt");
ObjectInputStream oin = new ObjectInputStream(fin);
此外ObjectInputStream还提供readObject()
方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,需要将它转换成合适的数据类型。
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
Demo1 out = (Demo1)in.readObject();
System.out.println(out); //输出对象
读取一下 test1.txt
里面的数据
DeSerialize.java
package com.seralize;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeSerialize {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.创建反序列化流
FileInputStream In = new FileInputStream("test1.txt");
ObjectInputStream fIn = new ObjectInputStream(In);
//2.使用ObjectInputStream中的readObject读取一个对象
Object o = fIn.readObject();
//3.释放所有资源
fIn.close();
System.out.println(o);
}
}
打印结果:
反序列化操作就是从二进制文件中提取对象
serialVersionUID
- 每个可序列化的类在序列化时都会关联一个版本号 , 这个版本号就是 serialVersionUID 属性
- serialVersionUID 属性必须通过 static final long 修饰符来修饰 。
- 如果可序列化的类未声明 serialVersionUID 属性 , 则 Java 序列化时会根据类的各种信息来计算默认的 serialVersionUID 值 . 但是 Oracle 官方文档强烈建议所有可序列化的类都显示声明 serialVersionUID 值 .
实例: 在Demo1.java 中定义serialVersionUID
其值为1
import java.io.Serializable;
public class Test implements Serializable {
public static final long serialVersionUID = 1L; //定义serialVersionUID
private String name;
private int age;
public Test() {
}
public Test(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Test{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
Serializable.java
package com.seralize;
import com.Muz1.Method.demo1;
import java.io.*;
public class Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.创建序列化流
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test2.txt"));
//2.写出对象
outputStream.writeObject(new Demo1("Muz1",20));
//3.释放资源
outputStream.close();
}
}
先运行Serializable.java
然后修改Demo1.java里的 seriaVersionUID = 2L
然后运行 DeSerialize.java
会报错
Transient (瞬态变量)
Transient( 瞬态变量 )是一个 Java 关键词 , 它用于标记类的成员变量在持久化到字节流时不要被序列化 ; 在通过网络套接字流传输字节流时 , transient 关键词标记的成员变量不会被序列化 。此为被static修饰的静态变量也不参与序列化操作。
实例: 将Demo1.java中的name
和age
变量前分别加上transient
和static
package com.seralize;
public class Demo1 implements java.io.Serializable {
public static final long serialVersionUID = 1L; //定义serialVersionUID
private transient String name; //加上transient
private static int age; //加上static
public Demo1() {
}
public Demo1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Demo1{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
然后运行 Serializable.java
和 DeSerialize.java
,
打印输出
"Muz1" --> "null"
20 --> 0
反序列化漏洞的基本原理
在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法被重写不当时产生漏洞
package com.seralize;
import java.io.*;
import java.io.Serializable;
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
//定义myObj对象
MyObjet myObjet = new MyObjet();
myObjet.name = "hi";
//创建一个包含对象的反序列化信息的"object"数据文件
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object"));
//writeObject()方法将myObj对象写入object文件
out.writeObject(myObjet);
out.close();
//反序列化
//从文件中反序列化obj对象
ObjectInputStream In = new ObjectInputStream(new FileInputStream("object"));
//恢复对象
MyObjet flag = (MyObjet)In.readObject();
System.out.println(flag.name);
In.close();
}
static class MyObjet implements Serializable{
public String name;
//重写read Object()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
//执行默认的readObject()方法
in.defaultReadObject();
//执行打开计算器程序命令
Runtime.getRuntime().exec("calc.exe");
}
}
}
此处重写了readObject方法,执行了 Runtime.getRuntime().exec()
defaultReadObject方法
为ObjectInputStream
中执行readObject
后的默认执行方法
运行流程:
1.myObj对象序列化进object文件
2.从object反序列化对象
3.调用readObject方法
4.执行Runtime.getRuntime().exec("calc.exe");
总结
- 类想要进行序列化操作,必须实现Serializable接口继承
- 强烈建议定义serialVersionUID ,序列化、反序列化前后不要修改其值
- 被static、Transient修饰的属性,不会进行序列化操作
readObject方法为ObjectInputStream中执行readObject后的默认执行方法