java反序列化学习

Serializeable

Seializeable是java实现序列化机制的工具,序列化数据包含对象类型和属性值。

Serializeable工具的简单说明

1
2
3
4
只需要实现Serializeable接口就可以进行对象的序列化处理
序列化对象可以是基本数据类型、集合类或其他对象
使用transientstatic关键字修饰的属性不会被序列化
父类不可序列化时,需要父类中存在无参构造函数。

相关接口和类

1
2
3
4
5
6
java.io.Serializable
java.io.Externalizable //该接口需要实现writeExternal和readExternal函数控制序列化
ObjectOutput
ObjectInput
ObjectOutputStream
ObjectInputStream

序列化

1
2
3
4
5
6
7
8
9
//创建OutputStream对象
OutputStream outputStream= new FileOutputStream("serial");
//将其封装到ObjectOutputStream对象中
ObjectOutputStream objectOutputStream=new ObjectOutputStream((outputStream));
//此后调用writeObject()对客完成对象的序列化,并将其发送给OutputStream
objectOutputStream.writeObject(Object);
//关闭资源
objectOutputStream.close();
outputStream.close();

反序列化

1
2
3
4
5
6
7
8
9
//创建OutputStream对象
InputStream inputStream=new FileInputStream("serial");
//将其封装到ObjectInputStream对象中
ObjectInputStream objectInputStream=new ObjectInputStream((inputStream));
//只需调用readObject(0即可完成对象的反序列化
objectInputStream.readObject();
//关闭资源
objectInputStream.close();
inputStream.close();

Serializable接口

一般一个类只要继承了Serializable接口,就代表该类和其子类都能进行JDK的序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.tq;

import java.io.*;

public class MyJavaSerialize {
public static class UInfo implements Serializable{
private String userName;
private int userAge;
private String userAddress;

public String getUserName(){return userName;};
public int getUserAge(){return userAge;};
public String getUserAddress(){return userAddress;};

public void setUserName(String userName){this.userName=userName;};
public void setUserAge(int userAge){this.userAge=userAge;};
public void setUserAddress(String userAddress){this.userAddress=userAddress;};
}

public static void main(String[] args) throws Exception {
UInfo userInfo=new UInfo();
userInfo.setUserAddress("a");
userInfo.setUserAge(22);
userInfo.setUserName("admin");

OutputStream outputStream=new FileOutputStream("serial");
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
outputStream.close();

InputStream inputStream=new FileInputStream("serial");
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
UInfo unserialUInfo=(UInfo) objectInputStream.readObject();
objectInputStream.close();
inputStream.close();

System.out.println(unserialUInfo.userName);
System.out.println(unserialUInfo.userAge);
System.out.println(unserialUInfo.userAddress);
}

}

输出结果,工程目录下会生成一个serial文件,其内容就是序列化后的数据

Externalizable接口

除了Serializable接口,java还提供了另一个序列化接口Externalizable,该接口继承自Serializable接口,但是有两个抽象函数:writeExternal和readExternal。需要自行实现两个函数来控制序列化流程,否则目标序列化类属性值会是类初始化后的默认值。

在使用Externalizable接口实现序列化时,读取对象会调用目标序列化类的无参构造函数去创建一个新的对象,再把徐磊话数据中的类属性值分别填充到新对象中。所以实现Externalizable接口的类必须提供一个public属性的无参构造函数

serialVersionUID

目标序列化类有一个隐藏属性

1
private static final long serialVerionUID

Java虚拟机判断是否允许序列化数据被序列化时,会取决于两个类的serialVersionUID是否一致。serialVerionUID在不同编译器内可能有不同的值,开发者可以在目标序列化类中提供固定值。在提高serialVerionUID固定值的情况下,只要序列化数据中国的serialVerionUID和目标序列化类中的一致就可以成功反序列化。如果没有指定值,编译器会根据class文件内容通过一定算法生成值,在不同环境下,编译器得到的serialVerionUID值不同,就会导致反序列化失败。改变目标类中代码也可能会影响生成的serialVersionUID,此时会抛出java.io.InvalidClassException并指出serialVersionUID不一致。建议在目标序列化类中显示定义serialVersionUID并赋予明确的值。

显示定义serialVersionUID有两种用途:

1.在某些场合,希望类的不同版本兼容序列化,所以需要确保类的不同版本有相同的serialVersionUID

2.某些时候不希望类的不同版本对序列化有兼容,所以需要类的不同版本有不同的serialVerionUID

反序列化漏洞

java有多种序列化和反序列化工具

1
2
3
JDK自带的Serializable
fastjson和jackson是JSON的知名反序列化工具
xmldecoder和xstream是XML的知名反序列化工具

java拥有完善的第三方类库和满足各种需求的框架,但因为很多第三方类库引用广泛,如果其中某些组件出现安全问题,那么受影响范围将极为广泛。

漏洞入口

ObjectInputStream对象的readObject函数调用是java反序列化流程的入口,序列化数据的来源包括:Cookie、GET参数、POST参数或者流、HTTP Head或者来自用户可控内容的数据库等。

触发场景:

1
2
3
4
1.HTTP请求中的参数
2.RMI,即Java远程方法调用,在RMI中传输的数据皆为序列化
3.JMX,一个为应用程序植入管理功能的框架
4.自定义协议 用来接收与发送原始的java对象

反序列化相关函数

1
2
3
4
5
6
7
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject

数据特征

序列化的数据头是不变的,传输过程中可能会对字节流进行编码,解码后查看字节流开头,反序列化数据开头包含两字节的魔术数字,后面是两字节的版本号,如ac ed 00 05,而经过Base64编码过序列化数据的字节流头部为Ro0AB,在攻击检测时可针对该特征进行匹配请求post中是否包含反序列化数据,判断是否为反序列化漏洞攻击。

利用形式

JDK原生反序列化工具Serializable大致有两种利用形式

1
2
3
4
5
1.完整对象前的利用
JDK对恶意序列化数据进行反序列化的过程中达成攻击效果,这种利用方式大多基于对java开发中频繁调用函数的理解,寻找漏洞触发点。例如commons-collections3.1反序列化漏洞利用中的rce gadget属于以readObject函数调用点为入口,直接在依赖包中寻找到rce的利用方式

2.生成完整对象的利用
如身份令牌反序列化,要等待对象反序列化完成后,利用其中的函数或者属性值完成攻击

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!