概述
序列化是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。
Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化。反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。
漏洞成因
序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。
漏洞代码示例如下:
......
//读取输入流,并转换对象
InputStream in=request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in);
//恢复对象
ois.readObject();
ois.close();
这里特别要注意的是非预期的对象,正因为此java标准库及大量第三方公共类库成为反序列化漏洞利用的关键。安全研究人员已经发现大量利用反序列化漏洞执行任意代码的方法,最让大家熟悉的是Gabriel Lawrence和Chris Frohoff在《Marshalling Pickles how deserializing objects can ruin your day》中提出的利用Apache Commons Collection实现任意代码执行。此后安全研究人员也陆续爆出XML、Json、Yaml等反序列化的相关漏洞。
除了commons-collections 3.1可以用来利用java反序列化漏洞,还有更多第三方库同样可以用来利用反序列化漏洞并执行任意代码,部分如下:
- commons-fileupload 1.3.1
- commons-io 2.4
- commons-collections 3.1
- commons-logging 1.2
- commons-beanutils 1.9.2
- org.slf4j:slf4j-api 1.7.21
- com.mchange:mchange-commons-java 0.2.11
- org.apache.commons:commons-collections 4.0
- com.mchange:c3p0 0.9.5.2
- org.beanshell:bsh 2.0b5
- org.codehaus.groovy:groovy 2.3.9
- …
Java反序列化详解
序列化数据结构
通过查看序列化后的数据,可以看到反序列化数据开头包含两字节的魔术数字,这两个字节始终为十六进制的0xAC ED。接下来是两字节的版本号0x00 05的数据。此外还包含了类名、成员变量的类型和个数等。
这里以类SerialObject示例来详细进行介绍Java对象序列化后的数据结构:
public class SerialObject implements Serializable{
private static final long serialVersionUID = 5754104541168322017L;
private int id;
public String name;
public SerialObject(int id,String name){
this.id=id;
this.name=name;
}
...
}
序列化SerialObject实例后以二进制格式查看:
00000000: aced 0005 7372 0024 636f 6d2e 7878 7878 ....sr.$com.xxxx
00000010: 7878 2e73 6563 2e77 6562 2e68 6f6d 652e xx.sec.web.home.
00000020: 5365 7269 616c 4f62 6a65 6374 4fda af97 SerialObjectO...
00000030: f8cc c5e1 0200 0249 0002 6964 4c00 046e .......I..idL..n
00000040: 616d 6574 0012 4c6a 6176 612f 6c61 6e67 amet..Ljava/lang
00000050: 2f53 7472 696e 673b 7870 0000 07e1 7400 /String;xp....t.
00000060: 0563 7279 696e 0a .cryin.
序列化的数据流以魔术数字和版本号开头,这个值是在调用ObjectOutputStream序列化时,由writeStreamHeader方法写入:
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);//STREAM_MAGIC (2 bytes) 0xACED
bout.writeShort(STREAM_VERSION);//STREAM_VERSION (2 bytes) 5
}
序列化后的SerialObject对象详细结构:
STREAM_MAGIC (2 bytes) 0xACED
STREAM_VERSION (2 bytes) 0x0005
TC_OBJECT (1 byte) 0x73
TC_CLASSDESC (1 byte) 0x72
className
length (2 bytes) 0x24 = 36
text (36 bytes) com.xxxxxx.sec.web.home.SerialObject
serialVersionUID (8 bytes) 0x4FDAAF97F8CCC5E1 = 5754104541168322017
classDescInfo
classDescFlags (1 byte) 0x02 = SC_SERIALIZABLE
fields
count (2 bytes) 2
field[0]
primitiveDesc