代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package serialize;
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 +
'}';
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package serialize;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class Serialization {
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("李二",22);
// System.out.println(person);
serialize(person);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package serialize;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Unserialization {
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);
}
}
|
Person.java

Serializable
接口在 Java 中用于实现对象的序列化和反序列化。序列化是将对象转换为字节流,方便传输或存储,反序列化是将字节流还原为对象,如果这里删掉,下面的序列化操作会报错
Serialization.java

这里是将序列化操作封装在一个方法体里, 将对象序列化成字节流并写入文件 ser.bin
Unserialization.java

读入ser.bin
的数据并将字节流反序列化为对象
运行后

(1) 序列化类的属性没有实现 Serializable那么在序列化就会报错
只有实现 了Serializable或者 Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常)
Serializable 接口是 Java 提供的序列化接口,它是一个空接口,所以其实我们不需要实现什么。
1
2
|
public interface Serializable {
}
|
Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。如果我们此处将 Serializable 接口删除掉的话,会导致如下结果。
(2) 在反序列化过程中,它的父类如果没有实现序列化接口,那么将需要提供无参构造函数来重新创建对象。
(3)一个实现 Serializable接口的子类也是可以被序列化的。
(4) 静态成员变量是不能被序列化
序列化是针对对象属性的,而静态成员变量是属于类的。
(5) transient 标识的对象成员变量不参与序列化
利用反序列化
在序列化的时候,需要用到writeObject(Object obj)
方法来 将对象序列化为字节流并写入 ObjectOutputStream
中
在反序列化的时候,需要用到readObject()
方法 从 ObjectInputStream
中读取字节流,并将其反序列化为一个对象
而readObject()
可以被重写,从而执行我们想要的代码
(1)入口类的直接调用危险方法
Person.java添加代码

运行序列化,再运行反序列化

这就相当于重写readObject
方法,从而弹计算器
其他的利用都是通过链子穿起来的
(2) 入口参数中包含可控类,该类有危险方法,readObject时调用
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,时调用
(4) 构造函数/静态代码块等类加载时隐式执行
产生漏洞的攻击路线
首先的攻击前提:继承 Serializable
入口类:source (重写 readObject 调用常见的函数;参数类型宽泛,比如可以传入一个类作为参数;最好 jdk 自带)
找到入口类之后要找调用链 gadget chain 相同名称、相同类型
执行类 sink (RCE SSRF 写文件等等)比如exec
这种函数
参考博客
[大佬的博客](Drun1baby/JavaSecurityLearning: 记录一下 Java 安全学习历程,也算是半条学习路线了)