详解Java反序列化漏洞

0×01:序列化基本概念

  • 序列化:将对象写入IO流中
  • 反序列化:从IO流中恢复对象
  • 意义:序列化机制允许将实现序列化的JAVA对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象 。序列化机制使得对象可以脱离程序的运行而独立存在 。
0×02:Java中的反射机制1. 反射机制的作用: 通过java语言中的反射机制可以操作字节码文件(可以读和修改字节码文件)
2. 反射机制的相关类在哪个包下java.lang.reflect.*;
3. 反射机制的相关类有哪些:
java.lang.Class 代表字节码文件,代表整个类
java.lang.reflect.Method 代表字节码中的方法字节码,代表类中的方法
java.lang.reflect.Constructor 代表字节码中的构造方法字节码,代表类中的构造方法java.lang.reflect.Field 代表字节码中的属性字节码,代表类中的属性 。
我们先看最主要的部分——执行系统命令
public class N0Tai1{public static void main(String[] args) throws Exception{} }Runtime calc = Runtime.getRuntime(); calc.exec("calc"); //Runtime.getRuntime().calc.exec("calc")相应的反射代码如下:
public class N0Tai1{public static void main(String[] args) throws Exception{} }Class c = Class.forName("java.lang.Runtime"); //c代表Runtime.class字节码文件,c代表Runtime类型Object obj = c.getMethod("getRuntime", null).invoke(c,null);/** 通过getMethod对getRuntime这个方法进行实例化* getRuntime并不需要传参,所以传参类型为null,后面的invoke实现getRuntime* */String[] n0tai1 = {"calc.exe"}; c.getMethod("exec",String.class).invoke(obj,n0tai1);/** getMethod对exec这个方法进行实例化* exec需要传一个String类型的字符串或者String类型的数组,然后invoke实现exec方法 * */0×03:序列化的实现方式序列化概述
如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现 Serializable 接口或者Externalizable接口之一 。
使用到JDK中关键类 :
ObjectOutputStream (对象输出流) 和 ObjectInputStream (对象输入流)ObjectOutputStream 类中:通过使用 writeObject (Object object) 方法,将对象以二进制格式进行写入 。
ObjectInputStream类中:
通过使用 readObject() 方法,从输入流中读取二进制流,转换成对象 。
Transient关键字序列化的时候不会序列化Transient关键字修饰的变量,这个关键字不能修饰类和方法Static 。
静态变量也不会被序列化
serialVersionUID
这里是指序列化的版本号,版本不一致会导致抛出错误,并且拒绝载入序列化与反序列化样例:
//Person.javapackage com.n0tai1.java.serialize;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream; import com.n0tai1.java.serialize.Student;public class Person{public static void main(String[] args) throws IOException {Student s = new Student(19,"ZAAAA"); System.out.println(s.Students()); System.out.println(s.toString()); ObjectOutputStream oos = new ObjectOutputStream(newFileOutputStream("I:\project\Java\JavaSePro\src\flag.txt")); oos.writeObject(s);oos.flush();oos.close(); }}//Student.javapackage com.n0tai1.java.serialize;import java.io.Serializable;public class Student implements Serializable {private static final long serialVersionUID = 5407396955208161433L;private int age;private transient String name;public Student(int age, String name){this.age = age;this.name = name; }public String Students(){return "姓名: "+ this.name + " 年龄: " + this.age;}@Overridepublic String toString() {return "姓名: "+ this.name + " 年龄: " + this.age;} }//unserialize.javapackage com.n0tai1.java.serialize;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;import com.n0tai1.java.serialize.Student;public class unserialize{public static void main(String[] args) throws IOException,ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(newFileInputStream("I:\project\Java\JavaSePro\src\flag.txt")); Object obj = ois.readObject();System.out.println(obj);ois.close(); }}现在已经知道如何序列化和反序列化了,我们把刚刚写的弹计算器代码序列化处理一下package com.n0tai1.java.serialize;import com.n0tai1.java.serialize.ExecTest; import java.io.FileOutputStream;import java.io.ObjectOutputStream;public class Serializable{public static void main(String[] args) throws Exception {ExecTest s = new ExecTest();s.ExecTest();ObjectOutputStream oos = new ObjectOutputStream(newFileOutputStream("I:\project\Java\JavaSePro\src\serialize.txt")); oos.writeObject(s);oos.flush();oos.close(); }}private transient String name;public Student(int age, String name){this.age = age;this.name = name; }public String Students(){return "姓名: "+ this.name + " 年龄: " + this.age;}@Overridepublic String toString() {return "姓名: "+ this.name + " 年龄: " + this.age;} }


推荐阅读