Java序列化与反序列化

Java序列化与反序列化

Java序列化是指把java对象转换为字节序列的过程。这一过程将数据分解成字节流,以便存储在文件中或是在网络中传输

Java反序列化是指把字节序列恢复为Java对象的过程。就是打开字节流并重构成对象

使用场景:

主要序列化的用途就是传递和存储


序列化的实现


需要序列化的类

User.java

package trrq;

import java.io.Serializable;

public class User implements Serializable {
private String name;


public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

序列化

UserSerializable.java

package trrq;

import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class UserSerializable {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("trrq");

//序列化,将对象转化为字节序列
serialize(user);
}

public static void serialize(User user) throws Exception{
FileOutputStream fout = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fout);
out.writeObject(user);
out.close();
fout.close();
System.out.println("序列化完成");
}
}

运行上面这个序列化程序后,就会生成user.ser文件


我们可以看下这个文件结构,ACED表示魔术头,0005表示版本号

序列化的数据以魔术数字和版本号开头,这个值是在调用ObjectOutputStream时,由writeStreamHeader写入。开头几位一般当作Java序列化字节的特征


反序列化


Serializable

package trrq;

import java.io.*;

public class UserSerializable {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("trrq");

//序列化,将对象转化为字节序列
// serialize(user);
//反序列化,将字节序列恢复为对象
User user1 = unserialize();
System.out.println(user1.getName());
}

public static void serialize(User user) throws Exception{
FileOutputStream fout = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fout);
out.writeObject(user);
out.close();
fout.close();
System.out.println("序列化完成");
}

public static User unserialize() throws Exception{
File file;
FileInputStream fileln = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileln);
User user = (User)in.readObject();
in.close();
fileln.close();
return user;
}
}

运行结果

读取序列化文件,将二进制文件重新恢复为user对象


Externalizable

通过实现Externalizable接口进行序列化和反序列化,但必须实现writeExternalreadExternal方法,并且还要实现一个类的无参构造方法Serializable 接口可以不用实现。

用法与Serializable是一致的

package trrq;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Evil implements Externalizable {

public Evil() {
System.out.println(this.getClass()+"的EvilClass的无参构造方法被调用");
}

public void writeExternal(ObjectOutput out) throws IOException {

}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

}
}

readObject()方法

该方法在反序列化漏洞起了大作用,在反序列化过程中,JVM虚拟机试图调用对象类里的writeObjectreadObject 方法,进行用户自定义的序列化和反序列化,如果没有这些方法,默认调用是ObjectOutputStreamdefaultWriteObject 方法以及 ObjectInputStreamdefaultReadObject 方法。用户定义的writeObjectreadObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。


Java反序列化的过程中可以自动执行序列化类的四个方法,实现了Serializable接口可以执行的方法包括readObjectreadObjectNoDatareadResolve,以及实现了Externalizable接口的readExternal方法。这些在找反序列化漏洞时都需要重点关注。


如果readObject方法书写不当的话就有可能引发恶意代码的执行,例如

package trrq;

import java.io.*;

public class EvilClass implements Serializable {

String name;
public EvilClass() {
System.out.println(this.getClass() + "的EvilClass()无参构造方法被调用!!!!!!");
}
public EvilClass(String name) {
System.out.println(this.getClass() + "的EvilClass(String name)构造方法被调用!!!!!!");
this.name = name;
}
public String getName() {
System.out.println(this.getClass() + "的getName被调用!!!!!!");
return name;
}
public void setName(String name) {
System.out.println(this.getClass() + "的setName被调用!!!!!!");
this.name = name;
}
@Override
public String toString() {
System.out.println(this.getClass() + "的toString()被调用!!!!!!");
return "EvilClass{" +
"name='" + getName() + '\'' +
'}';
}

//重写了readobject方法,并且调用了危险方法,name方法我们可控
private void readObject(ObjectInputStream in) throws Exception {
//执行默认的readObject()方法
in.defaultReadObject();
System.out.println(this.getClass() + "的readObject()被调用!!!!!!");
// windows
Runtime.getRuntime().exec(new String[]{"cmd", "/c", name});
// mac
// Runtime.getRuntime().exec(new String[]{"open", "-a", name});
}
}

序列化与反序列化

package trrq;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class EvilSerialize {
public static void main(String[] args) throws Exception {
EvilClass evilObj = new EvilClass();
//name属性可控
evilObj.setName("calc");
// mac
// evilObj.setName("Calculator");
// 序列化为字节数组
byte[] bytes = serializeToBytes(evilObj);
// 反序列化
EvilClass o = (EvilClass)deserializeFromBytes(bytes);
System.out.println(o);
}
public static byte[] serializeToBytes(final Object obj) throws Exception {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
objOut.flush();
objOut.close();
return out.toByteArray();
}
public static Object deserializeFromBytes(final byte[] serialized) throws Exception {
final ByteArrayInputStream in = new ByteArrayInputStream(serialized);
final ObjectInputStream objIn = new ObjectInputStream(in);
return objIn.readObject();
}
}

成功弹窗

在真实场景中,不会有人会写命令执行在readObject()中,反序列化通常会需要java的特性进行配合,然后通过利用链的寻找。反序列化漏洞需要三个东西

上一篇

Java反序列化-URLDNS