最近一直曝光的开源软件第三方反序列化漏洞:
CVE-2015-7501Commons Collections Java反序列化漏洞
Springframework 反序列化RCE漏洞
都是由于Java对象反序列化本身设计本身缺陷造成的
Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。Java API提供一种处理对象序列化和反序列化的标准机制。
一个对象能够序列化反序列的前提是实现Serializable,Externalizable接口,Ex:
import java.io.Serializable; public class test implements Serializable { private static final long serialVersionUID = -5809782578272943999L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } |
JVM本身提供了ObjectInputStream / ObjectOutputStream序列化反序列化对象
ObjectInputStream 在做反序列化的时候readSerialData中会通过类反射直接调用序列化对象里的readObject (java.io.ObjectInputStreams) 方法,如果不存在该方法,则将默认调用自身ObjectInputStream的defaultReadObject方法
在整个反序列话过程中,ObjectInputStream只对class是否在自己的classloader中进行了校验,并没有对将要反序列化的对象进行校验。
Ex:
ObjectInputStream ois2= new ObjectInputStream(fis); test myPerson = (test)ois2.readObject();
|
代码段中通常只是对Object强转成我们想反序列化的对象,通过这种方式来校验传入的序列化对象是否是test, 此时如果构建另一个序列化的对象 test2
import java.io.Serializable; public class test2 implements Serializable { private static final long serialVersionUID = -5809782578272943999L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } private void readObject(java.io.ObjectInputStream s) { s.defaultReadObject(); System.out.println("Hack!"); } } |
test myPerson = (test)ois2.readObject();
虽然会报错(类型不匹配),但是关键点是test2中的readObject已经调用了,如果这是一次攻击,在readObject 中已经执行了攻击代码
关键点:
1. 在服务器端Class load 能load的class
2. 序列化的对象自己实现了readObject方法
黑客重点关注的能反序列化的对象,常见的开源软件或者JVM自带的支持序列化的类,实现了危险的readObject的方法。
Java 本身有一层安全沙箱机制,在反序列化中权限控制只是实现了
1. enableSubclassImplementation 保护ObjectInputStream, 是否允许使用子类方式,继承和实现自定义的ObjectInputStream
2. enableSubstitution 是否允许在反序列对象后代替原反序列化对象
在ObjectInputStream 中resolveClass 里只是进行了class 是否能被load,自定义ObjectInputStream, 重载resolveClass的方法,对className 进行白名单校验
public final class SecureObjectInputStream extends ObjectInputStream{ public SecureObjectInputStream () throws SecurityException, IOException{ super(); } public SecureObjectInputStream (InputStream in) throws IOException { super(in); }
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException{ if(!desc.getName().equals("test")){ //白名单校验 throw new ClassNotFoundException(desc.getName()+" not find"); } returnsuper.resolveClass(desc); } }
|
可以通过resolveClass 的方法里实现对class name 进行白名单校验,如果是反序列话的类不在白名单之中的,直接抛出异常。
如果产品已经使用java 的安全沙盒,建议使用java安全沙箱机制进行防护
1. 设置enableSubclassImplementation
permission java.io.SerializablePermission"enableSubclassImplementation"; |
2. 自定义ObjectInputStream,重载resolveClass的方法
public final class SecureObjectInputStream extends ObjectInputStream{ public SecureObjectInputStream() throws SecurityException, IOException{ super(); } public SecureObjectInputStream(InputStream in) throws IOException { super(in);
} protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException{ SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission( new SerializablePermission("accessClass."+desc.getName())); } return super.resolveClass(desc); } }
|
在policy 文件里设置你的白名单
permission java.io.SerializablePermission "accessClass.test"; |