【技术分享】Java 序列化与反序列化安全分析

2017-09-19 14,852
 

1

基本介绍

Java 序列化是在 JDK 1.1 中引入的,是 Java 内核的重要特性之一。Java序列化将一个对象转换为流,反序列化则是将对象流转换为实际程序中使用的Java 对象的过程。序列化可以用于轻量级的持久化、通过 Sockets 进行传输、或者用于 Java RMI。

可序列化的对象需要实现 java.io.Serializable 接口或者 java.io.Externalizable 接口。

以实现 Serializable 接口为例,Serializable 仅是一个标记接口,并不包含任何需要实现的具体方法。实现该接口只是为了声明该Java类的对象是可以被序列化的。实际的序列化和反序列化工作是通过ObjectOuputStream和ObjectInputStream来完成的。ObjectOutputStream 的 writeObject 方法可以把一个Java对象写入到流中,ObjectInputStream 的 readObject 方法可以从流中读取一个 Java 对象。在写入和读取的时候,虽然用的参数或返回值是单个对象,但实际上操纵的是一个对象图,包括该对象所引用的其它对象,以及这些对象所引用的另外的对象。Java 会自动帮你遍历对象图并逐个序列化。除了对象之外,Java 中的基本类型和数组也是可以通过 ObjectOutputStream 和 ObjectInputStream 来序列化的。

示例代码:

Staff.java (用于序列化的类)

TestStaff.java (序列化测试类)

 

运行单元测试方法 testSerialize ,输出如下,说明成功进行了序列化与反序列化。

2

风险分析

由于对象序列化是遵循标准协议的,所以可以轻易地分析出流中的信息,并且可以进行篡改。协议文档 http://docs.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html 那么将会面临着以下的两种风险:

 

01

信息泄露

当序列化对象中存在敏感数据时,存在着信息泄露的风险。比如说上面例子中的 salary 字段,是公司的敏感信息,在序列化对象时候要特别注意。

以下是序列化后的数据,可以看到标出的数据为工资数据,即 100000 的十六进制表达。

dump 
02

数据篡改

恶意攻击者可以通过篡改序列化流中的数据达到以下目的:伪造、拒绝服务、命令执行等。

01

伪造

比较容易理解,就是篡改用户的 salary 字段,给自己多发些工资(修改上图中的 0186a0 即可)。

下面我将工资修改为 200000

当读取这个序列化对象时候,会将工资修改为 200000,想想就开心。

 

 

02

拒绝服务

当篡改的数据不符合序列化对象的格式要求时候,可能会导致在反序列化对象的过程中抛出异常,从而拒绝服务。

下面我们修改文件的头几个字节。

然后反序列化,会爆出异常,从而 dos

03

命令执行

当反序列化对象时的运行环境中存在有漏洞的 jar 包(比如 commons collectis 低版本),攻击者通过构造恶意数据流可以达到命令执行的效果。

新增以下的测试用例(我的环境中存在 commons-collections-3.1.jar,这个是有漏洞的 ),以弹出计算机为例:

其中 calc.ser 是使用 ysoserial 工具生成的 CommonsCollections 的命令执行序列化对象,生成方法:

java -jar ysoserial-0.0.4-all.jar CommonsCollections1 "calc" > calc.ser

此时,我们运行这个测试用例的时候,会直接弹出计算器。

3

缓解措施

01

通用措施

a、对序列化的流数据进行加密

b、在传输过程中使用 TLS 加密传输

c、对序列化数据进行完整性校验

 

02

针对信息泄露

使用 transient 标记敏感字段,这样敏感字段将不进行序列化

此时,我们再运行之前的测试程序,将无法得到 salary 字段的值。

 

03

针对数据篡改

 

01

针对序列化对象的属性

可以通过实现 validateObject 方法来进行对象属性值的校验。

步骤如下: 实现 ObjectInputValidation 接口并重写 validateObject  方法;实现 readObject 方法,并注册 Validation。

修改后的Staff.java代码如下:

此时我们将 Staff 的 id 设置为 -1,将会抛出异常。

 

 

02

针对整个对象伪造的

上面的方法虽然能够对字段进行验证,但其验证时机是在读取流之后,所以只能够对正常的序列化对象进行验证,对于畸形或者恶意序列化对象来说无能为例。

在序列化的流中,对象的描述是先于数据的,这就给了我们在读取流完成之前进行验证的机会。

方法是,通过重写 ObjectInputStream 的 resolveClass() 方法来实现。这样,我们需要自定义一个对象流读取类继承自 ObjectInputStream,代码如下:

然后,在反序列化的时候,使用自定义的 SecObjectInputStream 。

object

可以看到,成功阻断了计算器的弹出。

有人利用这个原理写了一个反序列化命令执行的防护工具,可以参考 https://github.com/ikkisoft/SerialKiller 。

 

 

 

4

参考文献

《Java序列化示例教程》  

http://www.importnew.com/14465.html

 

《Serialization in Java – Java Serialization》 

http://www.journaldev.com/2452/serialization-in-java

《Java Object Serialization》 

http://docs.oracle.com/javase/8/docs/technotes/guides/serialization/index.html

《对象序列化流协议》

http://docs.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html

《Java深度历险(十)——Java对象序列化与RMI》 

http://www.infoq.com/cn/articles/cf-java-object-serialization-rmi/

《怎么做才能让Java 序列化机制 更安全》 

http://www.myexception.cn/software-architecture-design/1463050.html

《Look-ahead Java deserialization》 

https://www.ibm.com/developerworks/library/se-lookahead/

《SerialKiller》 

https://github.com/ikkisoft/SerialKiller

 

本文作者:VSRC

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/60819.html

Tags:
评论  (0)
快来写下你的想法吧!

VSRC

文章数:28 积分: 50

感谢各位安全专家长期关注唯品会安全。

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号