• Java序列化和反序列的正常使用 √
  • Java反序列化readObject是如何构成漏洞的?
  • java反序列化的服务器环境中是否需要序列化的类的定义,才能触发反序列化漏洞?
  • 为何readObject复写是需要使用private属性并且传参java.io.ObjectInputStream这个特定的格式

序列化条件

  1. 该类必须实现java.io.Serializable或Externalizable接口

同时类中不是全部内容都是可以序列化的,还有不能被序列化的情况:

  1. 如果该类有父类,则分两种情况来考虑:
    • 如果该父类已经实现了可序列化接口,则其父类的相应字段及属性的处理和该类相同;
    • 如果该类的父类没有实现可序列化接口,则该类的父类所有的字段属性将不会序列化,并且反序列化时会调用父类的默认构造函数来初始化父类的属性,而子类却不调用默认构造函数,而是直接从流中恢复属性的值。
  2. 如果该类的某个属性标识为static类型的,则该属性不能序列化。
  3. 如果该类的某个属性采用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() {
    1. System.out.println("This is the "+this.identify+" of our company");
    } }
  1. ```java
  2. package com.serializable;
  3. import java.io.*;
  4. public class SerializableDemo {
  5. public static void main(String [] args)
  6. {
  7. Test e = new Test();
  8. e.name = "员工甲";
  9. e.identify = "General staff";
  10. try
  11. {
  12. // 打开一个文件输入流,等待反序列化后的二进制位文件的输入
  13. FileOutputStream fileOut = new FileOutputStream("D:\\java\\serializable\\test.db");
  14. // 建立对象输入流
  15. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  16. //输出序列化对象,实现对象序列化
  17. out.writeObject(e);
  18. out.close();
  19. fileOut.close();
  20. System.out.printf("Serialized data is saved in D:\\Task\\employee1.db");
  21. }catch(IOException i)
  22. {
  23. i.printStackTrace();
  24. }
  25. }
  26. }
  1. 得到二进制文件进行查看,**反序列化数据开头包含两字节的魔术数字**,这两个字节始终为十六进制的**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)
  2. <a name="PjIc0"></a>
  3. ## 如何序列化和反序列化
  4. > Java反序列化和PHP反序列化的区别
  5. > - PHP反序列化攻击是在序列化、反序列化de前后才进行攻击的,序列化和反序列的过程是一个独立的过程,并不能对其进行干扰,随其实PHP反序列化并不是由反序列化导致的,只是通过反序列化可以控制对象属性进而进行操作--需要触发魔法方法来进行调用,所以使用非常局限。
  6. > - JAVA程序却可以控制序列化和反序列的输入或读取内容,
  7. 1. 序列化一个java对象,首先是构建一个ObjectOutputStream对象<br />- 调用ObjectOutputStream对象的writeObject来序列化一个对象(此对象必须满足序列化条件
  8. 1. 反序列化java对象,首先得构建一个ObjectInputStream对象
  9. (调用readObject方法
  10. ```java
  11. import java.io.*;
  12. import java.nio.file.attribute.UserDefinedFileAttributeView;
  13. import java.util.Arrays;
  14. import java.util.Base64;
  15. public class trytry {
  16. public static void main(String [] args) throws IOException, ClassNotFoundException {
  17. //整一个需要序列化的对象
  18. user u = new user();
  19. u.setName("lala");
  20. //序列化输出至屏幕
  21. ObjectOutputStream out = new ObjectOutputStream(System.out);
  22. out.writeObject(u);
  23. System.out.println();
  24. //序列化写入文件
  25. FileOutputStream f = new FileOutputStream("test.bin");
  26. ObjectOutputStream fout = new ObjectOutputStream(f);
  27. fout.writeObject(u);
  28. //序列化写入到变量中
  29. ByteArrayOutputStream bOut = new ByteArrayOutputStream();
  30. ObjectOutputStream objOut = new ObjectOutputStream(bOut);
  31. objOut.writeObject(u);
  32. byte[] str= bOut.toByteArray();
  33. System.out.println(new String(str));
  34. //从变量中反序列化
  35. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(str));
  36. user u_d = (user) ois.readObject();
  37. System.out.println(u_d.getName());
  38. }
  39. }
  40. class user implements Serializable {
  41. private String name;
  42. public String getName() {return name; }
  43. public void setName(String name){this.name = name;}
  44. }

输出结果如下:
image.png

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 {

    1. //整一个需要序列化的对象
    2. user u = new user();
    3. u.setName("lala");
    4. //序列化写入文件
    5. FileOutputStream f = new FileOutputStream("test.bin");
    6. ObjectOutputStream fout = new ObjectOutputStream(f);
    7. fout.writeObject(u);

    class user implements Serializable { private String name;

    public String getName() {

    1. return name;

    }

    public void setName(String name){

    1. this.name = name;

    } private void readObject(java.io.ObjectInputStream in) throws IOException {

    1. 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(); } }

  1. ![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。
  2. > 反序列化原理探究:
  3. > 以上代码完成的事情:
  4. > - 创建了一个复写readObject函数的user
  5. > - 创建了user类实例
  6. > - 对其进行序列化,并存储进文件中
  7. > - 再将文件中的数据进行反序列化恢复成user
  8. >
  9. > 1. 注释掉java文件中user >报错找不到user
  10. > 1. 注释掉user类的所有接口,只留下user空壳 >class接口报错
  11. > 1. 注释掉user类中的readObject函数 >未报错 但未执行calc.exe
  12. > 1. 不注释user > 计算器弹框成功,成功调用user复写的readObject函数
  13. <a name="qM94m"></a>
  14. ### 反序列化攻击条件
  15. 利用readObject方法进行攻击,待补充
  16. ---
  17. ```java
  18. package com.serializable;
  19. import java.io.*;
  20. import java.io.IOException;
  21. import java.io.ObjectOutputStream;
  22. import java.util.Arrays;
  23. public class Person implements java.io.Serializable {
  24. public String name;
  25. public int age;
  26. Person(String name, int age) {
  27. this.name = name;
  28. this.age = age;
  29. }
  30. private void writeObject(java.io.ObjectOutputStream s) throws IOException {
  31. s.defaultWriteObject(); //将当前类的字段写入此流,只能从要序列化的类的writeObject方法中调用此方法
  32. s.writeObject("This is a object"); //在当前类已经写入的流中写入想要添加的数据段
  33. System.out.println();
  34. }
  35. private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
  36. s.defaultReadObject(); //从此流中读取当前类的字段,只能从要反序列化的类的readObject方法中调用此方法
  37. String message = (String) s.readObject(); //读取输入的字符串,储存在 objectAnnotation位置
  38. System.out.println(message);
  39. }
  40. public static void main(String[] args) throws IOException {
  41. Person person = new Person("sqy",111);
  42. ByteArrayOutputStream out = new ByteArrayOutputStream();
  43. ObjectOutputStream objout = new ObjectOutputStream(out);
  44. // ObjectOutputStream objout = new ObjectOutputStream(System.out);
  45. objout.writeObject(person);
  46. byte[] str=out.toByteArray();
  47. System.out.println(new String(str));
  48. ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(str));
  49. try { //使用try/catch语句捕获无法预知的异常报错
  50. in.readObject();
  51. } catch (ClassNotFoundException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }

输出:(写入的This is a Object被放在objectannotation的位置,反序列化时读取了这个字符串image.png


参考:

  1. https://lalajun.github.io/2019/08/20/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E5%9F%BA%E7%A1%80/ 反序列化基础