[java安全]-序列化反序列化


Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久化的目的。

java序列化: 对象 Java Object --> 二进制字节 byte[]

Java反序列化: 把一个二进制字节序列 byte[] --> Java对象 Java Object


序列化

ObjectOutputStream

字节输出流对象,将对象的输出流写到文件中(结合 FileOutputStream 使用)

Demo:

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));FileOutputStream fout = new FileOutputStream("1.txt");
ObjectOutputStream out = new ObjectOutputStream(fout);

此外ObjectOutputStream还提供了writeObject()方法来序列化一个对象,并将它发送到输出流。

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(new Demo1("Muz1",20));

序列化操作

一个对象要想序列化,必须满足两个条件:

  • 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任

    何状态序列化或反序列化,会抛出 NotSerializableException

  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用

    transient 关键字修饰。

实例:

Demo1.java

package com.seralize;

public class Demo1 implements java.io.Serializable {
    public String name;
    public String address;
    public transient int age;

    public void sout(){
        System.out.println("Sout :"+name+"--"+address);
    }

    public Demo1() {
        this.name = name;
        this.address = address;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Demo1{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                '}';
    }
}

Serializable.java

package com.seralize;

import com.Muz1.Method.demo1;

import java.io.*;

public class Serializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Demo1 demo1 = new Demo1();
        demo1.name = "Muz1";
        demo1.age = 20;
        demo1.address = "山东济南";
        //1.创建序列化流
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test1.txt"));
        //2.写出对象
        out.writeObject(demo1);
        //3.释放资源
        out.close();
    }
}

然后将Demo1对象写入到了test1.txt中

在这里插入图片描述

开头的AC ED 00 05为序列化内容的特征


反序列化

ObjectInputStream

字节输入流对象,将文件中的二进制字节序列进行反序列化操作(结合FileInputStream)

Demo:

ObjectInputStream in = new ObjectInputStream(new FileInputStream("1.txt"));FileInputStream fin = new FileInputStream("1.txt");
ObjectInputStream oin = new ObjectInputStream(fin);

此外ObjectInputStream还提供readObject()方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,需要将它转换成合适的数据类型。

ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
Demo1 out = (Demo1)in.readObject();
System.out.println(out);        //输出对象

读取一下 test1.txt 里面的数据

DeSerialize.java

package com.seralize;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeSerialize {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.创建反序列化流
        FileInputStream In = new FileInputStream("test1.txt");
        ObjectInputStream fIn = new ObjectInputStream(In);
        //2.使用ObjectInputStream中的readObject读取一个对象
        Object o = fIn.readObject();
        //3.释放所有资源
        fIn.close();
        System.out.println(o);
    }
}

打印结果:

反序列化操作就是从二进制文件中提取对象


serialVersionUID

  • 每个可序列化的类在序列化时都会关联一个版本号 , 这个版本号就是 serialVersionUID 属性
  • serialVersionUID 属性必须通过 static final long 修饰符来修饰 。
  • 如果可序列化的类未声明 serialVersionUID 属性 , 则 Java 序列化时会根据类的各种信息来计算默认的 serialVersionUID 值 . 但是 Oracle 官方文档强烈建议所有可序列化的类都显示声明 serialVersionUID 值 .

实例: 在Demo1.java 中定义serialVersionUID 其值为1

import java.io.Serializable;

public class Test implements Serializable {
    public static final long serialVersionUID = 1L;   //定义serialVersionUID 

    private  String name;
    private  int age;

    public Test() {
    }

    public Test(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Test{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

}

Serializable.java

package com.seralize;

import com.Muz1.Method.demo1;

import java.io.*;

public class Serializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
		//1.创建序列化流
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test2.txt"));
        //2.写出对象
        outputStream.writeObject(new Demo1("Muz1",20));
        //3.释放资源
        outputStream.close();
    }
}

先运行Serializable.java

然后修改Demo1.java里的 seriaVersionUID = 2L

在这里插入图片描述

然后运行 DeSerialize.java 会报错

img


Transient (瞬态变量)

Transient( 瞬态变量 )是一个 Java 关键词 , 它用于标记类的成员变量在持久化到字节流时不要被序列化 ; 在通过网络套接字流传输字节流时 , transient 关键词标记的成员变量不会被序列化 。此为被static修饰的静态变量也不参与序列化操作。

实例: 将Demo1.java中的nameage变量前分别加上transientstatic

package com.seralize;

public class Demo1 implements java.io.Serializable {
    public static final long serialVersionUID = 1L;  //定义serialVersionUID
    private transient String name;					 //加上transient
    private static int age;							 //加上static

    public Demo1() {
    }

    public Demo1(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Demo1{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

}

然后运行 Serializable.javaDeSerialize.java,

打印输出

"Muz1" --> "null"
20 --> 0

在这里插入图片描述


反序列化漏洞的基本原理

在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法被重写不当时产生漏洞

package com.seralize;

import java.io.*;
import java.io.Serializable;

public class Demo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //序列化
        //定义myObj对象
        MyObjet myObjet = new MyObjet();
        myObjet.name = "hi";
        //创建一个包含对象的反序列化信息的"object"数据文件
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object"));
        //writeObject()方法将myObj对象写入object文件
        out.writeObject(myObjet);
        out.close();

        //反序列化
        //从文件中反序列化obj对象
        ObjectInputStream In = new ObjectInputStream(new FileInputStream("object"));
        //恢复对象
        MyObjet flag = (MyObjet)In.readObject();
        System.out.println(flag.name);
        In.close();
    }

    static class MyObjet implements Serializable{
        public String name;
        //重写read Object()方法
        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
            //执行默认的readObject()方法
            in.defaultReadObject();
            //执行打开计算器程序命令
            Runtime.getRuntime().exec("calc.exe");
        }
    }
}

此处重写了readObject方法,执行了 Runtime.getRuntime().exec()

defaultReadObject方法ObjectInputStream中执行readObject后的默认执行方法

运行流程:

1.myObj对象序列化进object文件
2.从object反序列化对象
3.调用readObject方法
4.执行Runtime.getRuntime().exec("calc.exe");

在这里插入图片描述


总结

  • 类想要进行序列化操作,必须实现Serializable接口继承
  • 强烈建议定义serialVersionUID ,序列化、反序列化前后不要修改其值
  • 被static、Transient修饰的属性,不会进行序列化操作
    readObject方法为ObjectInputStream中执行readObject后的默认执行方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值