- Java序列化和反序列的正常使用 √
- Java反序列化readObject是如何构成漏洞的?
- java反序列化的服务器环境中是否需要序列化的类的定义,才能触发反序列化漏洞?
- 为何readObject复写是需要使用private属性并且传参java.io.ObjectInputStream这个特定的格式
序列化条件
- 该类必须实现java.io.Serializable或Externalizable接口
同时类中不是全部内容都是可以序列化的,还有不能被序列化的情况:
- 如果该类有父类,则分两种情况来考虑:
- 如果该父类已经实现了可序列化接口,则其父类的相应字段及属性的处理和该类相同;
- 如果该类的父类没有实现可序列化接口,则该类的父类所有的字段属性将不会序列化,并且反序列化时会调用父类的默认构造函数来初始化父类的属性,而子类却不调用默认构造函数,而是直接从流中恢复属性的值。
- 如果该类的某个属性标识为static类型的,则该属性不能序列化。
- 如果该类的某个属性采用transient关键字标识,则该属性不能序列化。
若想知道一个java标准类是否是可序列化的,可以通过查看该类的文档,查看该类有没有实现java.io.serializable接口。
序列化
- 一个小Demo
```java
package com.serializable;
public class Test implements java.io.Serializable{
public String name;
public String identify;
public void mailCheck()
{
} }System.out.println("This is the "+this.identify+" of our company");
```java
package com.serializable;
import java.io.*;
public class SerializableDemo {
public static void main(String [] args)
{
Test e = new Test();
e.name = "员工甲";
e.identify = "General staff";
try
{
// 打开一个文件输入流,等待反序列化后的二进制位文件的输入
FileOutputStream fileOut = new FileOutputStream("D:\\java\\serializable\\test.db");
// 建立对象输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//输出序列化对象,实现对象序列化
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in D:\\Task\\employee1.db");
}catch(IOException i)
{
i.printStackTrace();
}
}
}
得到二进制文件进行查看,**反序列化数据开头包含两字节的魔术数字**,这两个字节始终为十六进制的**0xACED**,接下来是0x0005的**版本号数据**,此外还包含了**类名,成员变量**的**类型**和**个数**。<br />![](https://cdn.nlark.com/yuque/0/2021/png/2870855/1621672923564-58e791c6-7c23-4315-a18f-1607e64feaec.png#height=419&id=ZVIW1&originHeight=419&originWidth=1528&originalType=binary&ratio=1&status=done&style=none&width=1528)
<a name="PjIc0"></a>
## 如何序列化和反序列化
> Java反序列化和PHP反序列化的区别
> - PHP反序列化攻击是在序列化、反序列化de前后才进行攻击的,序列化和反序列的过程是一个独立的过程,并不能对其进行干扰,随其实PHP反序列化并不是由反序列化导致的,只是通过反序列化可以控制对象属性进而进行操作--需要触发魔法方法来进行调用,所以使用非常局限。
> - JAVA程序却可以控制序列化和反序列的输入或读取内容,
1. 序列化一个java对象,首先是构建一个ObjectOutputStream对象<br />- 调用ObjectOutputStream对象的writeObject来序列化一个对象(此对象必须满足序列化条件
1. 反序列化java对象,首先得构建一个ObjectInputStream对象
(调用readObject方法
```java
import java.io.*;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.util.Arrays;
import java.util.Base64;
public class trytry {
public static void main(String [] args) throws IOException, ClassNotFoundException {
//整一个需要序列化的对象
user u = new user();
u.setName("lala");
//序列化输出至屏幕
ObjectOutputStream out = new ObjectOutputStream(System.out);
out.writeObject(u);
System.out.println();
//序列化写入文件
FileOutputStream f = new FileOutputStream("test.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(u);
//序列化写入到变量中
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(bOut);
objOut.writeObject(u);
byte[] str= bOut.toByteArray();
System.out.println(new String(str));
//从变量中反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(str));
user u_d = (user) ois.readObject();
System.out.println(u_d.getName());
}
}
class user implements Serializable {
private String name;
public String getName() {return name; }
public void setName(String name){this.name = name;}
}
输出结果如下:
Java在序列化时一个对象,将会调用这个对象中的 writeObject 方法,参数类型是 ObjectOutputStream ,开发者可以将任何内容写入这个stream中;反序列化时,会调用 readObject ,开发者也可以从中读取出前面写入的内容,并进行处理。
漏洞原理
漏洞的关键在于readObject()方法: ```java package com.serializable; import java.io.*; import java.nio.file.attribute.UserDefinedFileAttributeView; import java.util.Arrays; import java.util.Base64; public class trytry { public static void main(String [] args) throws IOException, ClassNotFoundException {
//整一个需要序列化的对象
user u = new user();
u.setName("lala");
//序列化写入文件
FileOutputStream f = new FileOutputStream("test.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(u);
class user implements Serializable { private String name;
public String getName() {
return name;
}
public void setName(String name){
this.name = name;
} private void readObject(java.io.ObjectInputStream in) throws IOException {
Runtime.getRuntime().exec("calc.exe");
} }
public class UnSerializableDemo { public static void main(String[] args) throws Exception{ ObjectInputStream input = new ObjectInputStream(new FileInputStream(“test.bin”)); input.readObject(); } }
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1621753259095-6482f221-4e7a-4a90-8152-87c7414de01b.png#height=352&id=KTkFQ&margin=%5Bobject%20Object%5D&name=image.png&originHeight=704&originWidth=1604&originalType=binary&ratio=1&size=121687&status=done&style=none&width=802)<br />在反序列化执行时调用的readObject方法是我们在trytry.java中复写的方法(序列化存储进了文件中),进而调用calc.exe。
> 反序列化原理探究:
> 以上代码完成的事情:
> - 创建了一个复写readObject函数的user类
> - 创建了user类实例
> - 对其进行序列化,并存储进文件中
> - 再将文件中的数据进行反序列化恢复成user类
>
> 1. 注释掉java文件中user类 >报错找不到user类
> 1. 注释掉user类的所有接口,只留下user空壳 >class接口报错
> 1. 注释掉user类中的readObject函数 >未报错 但未执行calc.exe
> 1. 不注释user > 计算器弹框成功,成功调用user复写的readObject函数
<a name="qM94m"></a>
### 反序列化攻击条件
利用readObject方法进行攻击,待补充
---
```java
package com.serializable;
import java.io.*;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;
public class Person implements java.io.Serializable {
public String name;
public int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject(); //将当前类的字段写入此流,只能从要序列化的类的writeObject方法中调用此方法
s.writeObject("This is a object"); //在当前类已经写入的流中写入想要添加的数据段
System.out.println();
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject(); //从此流中读取当前类的字段,只能从要反序列化的类的readObject方法中调用此方法
String message = (String) s.readObject(); //读取输入的字符串,储存在 objectAnnotation位置
System.out.println(message);
}
public static void main(String[] args) throws IOException {
Person person = new Person("sqy",111);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objout = new ObjectOutputStream(out);
// ObjectOutputStream objout = new ObjectOutputStream(System.out);
objout.writeObject(person);
byte[] str=out.toByteArray();
System.out.println(new String(str));
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(str));
try { //使用try/catch语句捕获无法预知的异常报错
in.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出:(写入的This is a Object被放在objectannotation的位置,反序列化时读取了这个字符串
参考: