- 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");
```javapackage 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 /><a name="PjIc0"></a>## 如何序列化和反序列化> Java反序列化和PHP反序列化的区别> - PHP反序列化攻击是在序列化、反序列化de前后才进行攻击的,序列化和反序列的过程是一个独立的过程,并不能对其进行干扰,随其实PHP反序列化并不是由反序列化导致的,只是通过反序列化可以控制对象属性进而进行操作--需要触发魔法方法来进行调用,所以使用非常局限。> - JAVA程序却可以控制序列化和反序列的输入或读取内容,1. 序列化一个java对象,首先是构建一个ObjectOutputStream对象<br />- 调用ObjectOutputStream对象的writeObject来序列化一个对象(此对象必须满足序列化条件1. 反序列化java对象,首先得构建一个ObjectInputStream对象(调用readObject方法```javaimport 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(); } }
<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方法进行攻击,待补充---```javapackage 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的位置,反序列化时读取了这个字符串
参考:
