@Author: Patrilic
@Time: 2020-3-18 11:26:55

0x00 前言

Java中只需要实现java.io.Serializable或者java.io.Externalizable接口即可执行序列化操作

0x01 构造序列化/反序列化操作

构造一个调用类,其中,Exployee类实现了java.io.Serializable接口 :

1
2
3
4
5
6
7
8
9
10
11
package com.patrilic.testSer;

public class Employee implements java.io.Serializable
{
public String name;
public String identify;
public void mailCheck()
{
System.out.println("This is the "+this.identify+" of our company");
}
}

序列化操作, 在调用Employee类时,所有的数据都会被序列化

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
26
27
package com.patrilic.testSer;
import com.patrilic.testSer.Employee;

//import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader.Array;
import java.io.*;
public class Step1 {
public static void main(String [] args) {
Employee e = new Employee();
e.name = "员工甲";
e.identify = "General staff";
try {
// 打开一个文件输入流
FileOutputStream fileOut =
new FileOutputStream("/tmp/test.db");
// 建立对象输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//输出反序列化对象
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in /tmp/test.db");
}catch(IOException i)
{
i.printStackTrace();
}
}
}

反序列化操作

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
26
27
28
29
30
31
32
33
34
35
package com.patrilic.testSer;
import java.io.*;
public class Step2
{
public static void main(String [] args)
{

Employee e = null;
try
{
// 打开一个文件输入流
FileInputStream fileIn = new FileInputStream("/tmp/test.db");
// 建立对象输入流
ObjectInputStream in = new ObjectInputStream(fileIn);
// 读取对象
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("This is the "+e.identify+" of our company");


}
}

5c76564b213c37611a5e44668313a8bd

0x02 反序列化漏洞

Java反序列化中,会调用被反序列化的readObject方法,如果readObject方法是恶意的,那么就会引发漏洞

Demo

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
26
27
28
29
30
31
32
33
34
35
package com.patrilic.testSer;
import java.io.*;

public class test{
public static void main(String args[]) throws Exception{

UnsafeClass Unsafe = new UnsafeClass();
Unsafe.name = "hacked by ph0rse";

FileOutputStream fos = new FileOutputStream("object");
ObjectOutputStream os = new ObjectOutputStream(fos);
//writeObject()方法将Unsafe对象写入object文件
os.writeObject(Unsafe);
os.close();
//从文件中反序列化obj对象
FileInputStream fis = new FileInputStream("object");
ObjectInputStream ois = new ObjectInputStream(fis);
//恢复对象
UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject();
System.out.println(objectFromDisk.name);
ois.close();
}
}

class UnsafeClass implements Serializable{
public String name;
//重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
//执行默认的readObject()方法
in.defaultReadObject();
//执行命令
System.out.println("my First!");
Runtime.getRuntime().exec("open -a Calculator.app");
}
}

这里进行反序列化操作时,使用ObjectInputStream读取序列化文件,然后调用了readObject(), 当然就会成功的执行命令
91feac2fe936fc44a74b4558c29b5774

并且确实是先执行了readObject类的操作,然后再进行System.out.println(name)

ObjectInputStream && ObjectOutputStream

序列化对象: java.io.ObjectOutputStream->writeObject()

反序列化对象: java.io.ObjectInputStream->readObject()

也就是说在序列化类时,就会自动去

Serializable

353fe18d91d94771da7b2851b01803c5

java.io.Serializable接口是空接口,仅仅用于表示这个类可序列化

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.patrilic.demo;
import java.io.*;
import java.util.Arrays;
public class DeserializationTest implements Serializable {
private String username;
private String email;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}

public static void main(String[] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try {
// 创建DeserializationTest类,并类设置属性值
DeserializationTest t = new DeserializationTest();
t.setUsername("Patrilic");
t.setEmail("admin@patrilic.top");

// 创建Java对象序列化输出流对象
ObjectOutputStream out = new ObjectOutputStream(baos);

// 序列化DeserializationTest类
out.writeObject(t);
out.flush();
out.close();

// 打印DeserializationTest类序列化以后的字节数组,我们可以将其存储到文件中或者通过Socket发送到远程服务地址
System.out.println("DeserializationTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray()));

// 利用DeserializationTest类生成的二进制数组创建二进制输入流对象用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

// 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
ObjectInputStream in = new ObjectInputStream(bais);

// 反序列化输入流数据为DeserializationTest对象
DeserializationTest test = (DeserializationTest) in.readObject();
System.out.println("用户名:" + test.getUsername() + ",邮箱:" + test.getEmail());

// 关闭ObjectInputStream输入流
in.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

}

76d9756e4aab867670c7fcda970c3c24

  • 使用了ObjectOutputStream->writeObject()序列化了DeserializationTest类
  • 使用了ObjectInputStream->readObject()反序列化了DeserializationTest类

Externalizable

7d55817c04f81057ababfb388229de68

java.io.Externalizable继承Serializable接口,定义了两个方法:

  • writeExternal()
  • readExternal()
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.patrilic.demo;

import java.io.*;
import java.util.Arrays;


public class ExternalizableTest implements java.io.Externalizable {

private String username;
private String email;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(username);
out.writeObject(email);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.username = (String) in.readObject();
this.email = (String) in.readObject();
}

public static void main(String[] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try {
// 创建DeserializationTest类,并类设置属性值
ExternalizableTest t = new ExternalizableTest();
t.setUsername("Patrilic");
t.setEmail("admin@patrilic.top");

// 创建Java对象序列化输出流对象
ObjectOutputStream out = new ObjectOutputStream(baos);

// 序列化DeserializationTest类
out.writeObject(t);
out.flush();
out.close();

// 打印ExternalizableTest类序列化以后的字节数组,我们可以将其存储到文件中或者通过Socket发送到远程服务地址
System.out.println("ExternalizableTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray()));

// 利用ExternalizableTest类生成的二进制数组创建二进制输入流对象用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

// 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
ObjectInputStream in = new ObjectInputStream(bais);

// 反序列化输入流数据为DeserializationTest对象
ExternalizableTest test = (ExternalizableTest) in.readObject();
System.out.println("用户名:" + test.getUsername() + ",邮箱:" + test.getEmail());

// 关闭ObjectInputStream输入流
in.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

}

dbab49a33c3f7b5d91ac12675c425caf

值得注意的是两个方法的重写:
ad0bec7b07149615601539047867463f

0x03 相关链接

https://xz.aliyun.com/t/2041
https://javasec.org/javase/JavaDeserialization/Serialization.html
https://xz.aliyun.com/t/2043