Java反序列化漏洞
2022-03-22 10:21:00 # Java安全

序列化与反序列化基础

概念

Java序列化是指把Java对象转换为字节序列的过程;

Java反序列化是指把字节序列恢复为Java对象的过程;

image-20220322102504287

作用

序列化最重要的作用:在传递和保存对象时,保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。

反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。

总结:核心作用就是对象状态的保存和重建。(整个过程核心点就是字节流中所保存的对象状态及描述信息)

优点:

①将对象转为字节流存储到硬盘上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。

②序列化成字节流形式的对象可以进行网络传输(二进制形式),方便了网络传输。

③通过序列化可以在进程间传递对象。

实现

只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常)

JDK中序列化和反序列化的API:

  1. java.io.ObjectInputStream:对象输入流。该类的readObject()方法从输入流中读取字节序列,然后将字节序列反序列化为一个对象并返回。
  2. java.io.ObjectOutputStream:对象输出流。该类的writeObject(Object obj)方法将将传入的obj对象进行序列化,把得到的字节序列写入到目标输出流中进行输出。

三种实现:

  1. 若Student类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化.

    1
    2
    ObjectOutputStream采用默认的序列化方式,对Student对象的非transient的实例变量进行序列化。 
    ObjcetInputStream采用默认的反序列化方式,对Student对象的非transient的实例变量进行反序列化
  2. 若Student类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)writeObject(ObjectOutputSteam out),则按照以下方式进行序列化与反序列化。

    1
    2
    ObjectOutputStream调用Student对象的writeObject(ObjectOutputStream out)的方法进行序列化。 
    ObjectInputStream会调用Student对象的readObject(ObjectInputStream in)的方法进行反序列化。
  3. 若Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。

    1
    2
    ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。 
    ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。

示例

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
package ser;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class serdemo implements Serializable{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
public class serdemo1 {
public static void main(String args[]) throws IOException, ClassNotFoundException {
#序列化
FileOutputStream fileOutputStream = new FileOutputStream("seri.txt");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
serdemo serdemo=new serdemo();
serdemo.setName("test");
outputStream.writeObject(serdemo);
#反序列化
FileInputStream fileInputStream = new FileInputStream("seri.txt");
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
serdemo object2 = (serdemo) inputStream.readObject();
System.out.println("反序列化后的对象的值");
System.out.println(object2.getName());
inputStream.close();
}
}

注意点

  1. 序列化时,只对对象的状态进行保存,而不管对象的方法;
  2. 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
  3. 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
  4. 并非所有的对象都可以序列化;
  5. 声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据;
  6. 如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存。

漏洞原理

在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法被重写不当时产生漏洞,这里为什么会调用反序列化类的readobject方法,后续做分析

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
public class demon {

public static void main(String args[]) throws Exception{
//序列化
//定义myObj对象
MyObject myObj = new MyObject();
myObj.name = "test";
//创建一个包含对象进行反序列化信息的”object”数据文件
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("object"));
//writeObject()方法将myObj对象写入object文件
os.writeObject(myObj);
os.close();
//反序列化
//从文件中反序列化obj对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object"));
//恢复对象
MyObject objectFromDisk = (MyObject)ois.readObject();
System.out.println(objectFromDisk.name);
ois.close();
}
static class MyObject implements Serializable {
public String name;
//重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
//执行默认的readObject()方法
in.defaultReadObject();
//执行打开计算器程序命令
Runtime.getRuntime().exec("calc.exe");

}

}

}

基础库中存在的反序列化漏洞

1
2
3
4
5
6
7
8
9
10
11
12
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
org.springframework:spring-aop 4.1.4.RELEASE

漏洞挖掘

反序列化操作一般应用在导入模板文件、网络通信、数据传输、日志格式化存储、对象数据落磁盘、或DB存储等业务场景。

白盒检测

① 通过检索源码中对反序列化函数的调用来静态寻找反序列化的输入点

  • 搜索以下函数:
1
2
3
4
5
6
7
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject

② 确定了反序列化输入点后,再考察应用的Class Path中是否包含Apache Commons Collections等危险库(ysoserial所支持的其他库亦可)。
③ 若不包含危险库,则查看一些涉及命令、代码执行的代码区域,防止程序员代码不严谨,导致bug。
④ 若包含危险库,则使用ysoserial进行攻击复现。

黑盒检测

  • 通过抓包来检测请求中可能存在的序列化数据。
  • 序列化数据通常以AC ED开始,之后的两个字节是版本号,版本号一般是00 05AC ED 00 05经过Base64编码之后为rO0AB
  • 十六进制对照表:
1
2
3
4
5
6
7
8
9
10
11
0x70 - TC_NULL
0x71 - TC_REFERENCE
0x72 - TC_CLASSDESC
0x73 - TC_OBJECT
0x74 - TC_STRING
0x75 - TC_ARRAY
0x76 - TC_CLASS
0x7B - TC_EXCEPTION
0x7C - TC_LONGSTRING
0x7D - TC_PROXYCLASSDESC
0x7E - TC_ENUM

可以通过tcpdump抓取TCP/HTTP请求,通过SerialBrute.py去自动化检测,并插入ysoserial生成的exp

修复方法

通过Hook resolveClass来校验反序列化的类

在readObject反序列化时首先会调用resolveClass读取反序列化的类名,所以这里通过重写ObjectInputStream对象的resolveClass方法即可实现对反序列化类的校验。通过此方法,可灵活的设置允许反序列化类的白名单,也可设置不允许反序列化类的黑名单。但反序列化漏洞利用方法一直在不断的被发现,黑名单需要一直更新维护,且未公开的利用方法无法覆盖。

检测示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AntObjectInputStream extends ObjectInputStream{
public AntObjectInputStream(InputStream inputStream)
throws IOException {
super(inputStream);
}

/**
* 只允许反序列化SerialObject class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,
ClassNotFoundException {
if (!desc.getName().equals(SerialObject.class.getName())) {
throw new InvalidClassException(
"Unauthorized deserialization attempt",
desc.getName());
}
return super.resolveClass(desc);
}
}

使用ValidatingObjectInputStream来校验反序列化的类

使用Apache Commons IO Serialization包中的ValidatingObjectInputStream类的accept方法来实现反序列化类白/黑名单控制

1
2
3
4
5
6
7
8
9
10
11
12
private static Object deserialize(byte[] buffer) throws IOException,
ClassNotFoundException , ConfigurationException {
Object obj;
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
// Use ValidatingObjectInputStream instead of InputStream
ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais);

//只允许反序列化SerialObject class
ois.accept(SerialObject.class);
obj = ois.readObject();
return obj;
}

使用ObjectInputFilter来校验反序列化的类

Java 9包含了支持序列化数据过滤的新特性,开发人员也可以继承java.io.ObjectInputFilter类重写checkInput方法实现自定义的过滤器,并使用ObjectInputStream对象的setObjectInputFilter设置过滤器来实现反序列化类白/黑名单控制。

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
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.io.ObjectInputFilter;
class BikeFilter implements ObjectInputFilter {
private long maxStreamBytes = 78; // Maximum allowed bytes in the stream.
private long maxDepth = 1; // Maximum depth of the graph allowed.
private long maxReferences = 1; // Maximum number of references in a graph.
@Override
public Status checkInput(FilterInfo filterInfo) {
if (filterInfo.references() < 0 || filterInfo.depth() < 0 || filterInfo.streamBytes() < 0 || filterInfo.references() > maxReferences || filterInfo.depth() > maxDepth|| filterInfo.streamBytes() > maxStreamBytes) {
return Status.REJECTED;
}
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
if (SerialObject.class == filterInfo.serialClass()) {
return Status.ALLOWED;
}
else {
return Status.REJECTED;
}
}
return Status.UNDECIDED;
} // end checkInput
} // end class BikeFilter

黑名单校验修复

  • org.apache.commons.collections.functors.InvokerTransformer
  • org.apache.commons.collections.functors.InstantiateTransformer
  • org.apache.commons.collections4.functors.InvokerTransformer
  • org.apache.commons.collections4.functors.InstantiateTransformer
  • org.codehaus.groovy.runtime.ConvertedClosure
  • org.codehaus.groovy.runtime.MethodClosure
  • org.springframework.beans.factory.ObjectFactory
  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  • org.apache.commons.fileupload
  • org.apache.commons.beanutils

安全编码建议

  • 更新commons-collections、commons-io等第三方库版本;
  • 业务需要使用反序列化时,尽量避免反序列化数据可被用户控制,如无法避免建议尽量使用白名单校验的修复方式;

参考

https://blog.csdn.net/tree_ifconfig/article/details/82766587

https://www.cnblogs.com/nice0e3/p/14127885.html

https://www.cnblogs.com/yyhuni/p/14755940.html

https://github.com/Cryin/Paper/blob/master/%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8:JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E4%B9%8B%E6%AE%87.md