反序列化漏洞最近一直不得安宁,先有Apache Commons Collections通过反序列化实现远程代码执行,再有Spring RMI 反序列化漏洞,最新又有了common upload file的反序列化漏洞CVE-2016-1000031(/cgi-bin/?name=CVE-2016-1000031)
漏洞原因
先来看 代码
/**
* Reads the state of this object during deserialization.
*
* @param in The stream from which the state should be read.
*
* @throws IOException if an error occurs.
* @throws ClassNotFoundException if class cannot be found.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
// read values
();
/* One expected use of serialization is to migrate HTTP sessions
* containing a DiskFileItem between JVMs. Particularly if the JVMs are
* on different machines It is possible that the repository location is
* not valid so validate it.
*/
if (repository != null) {
if (()) {
// Check path for nulls
if (().contains("\0")) {
throw new IOException(format(
"The repository [%s] contains a null character",
()));
}
} else {
throw new IOException(format(
"The repository [%s] is not a directory",
()));
}
}
OutputStream output = getOutputStream();
if (cachedContent != null) {
(cachedContent);
} else {
FileInputStream input = new FileInputStream(dfosFile);
(input, output);
();
dfosFile = null;
}
();
cachedContent = null;
}
DiskFileItem里面封装了反序列的方法,在反序列化的方法里
1. 序列化中的数据可以保存在服务器的临时文件中
2. 复制服务器的文件到临时文件
3. 删除服务器的文件
private transient DeferredFileOutputStream dfos;
public OutputStream getOutputStream()
throws IOException {
if (dfos == null) {
File outputFile = getTempFile();
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
}
return dfos;
}
dfos 是不可序列化的,反序列化中dfos为空,只能获取临时文件
protected File getTempFile() {
if (tempFile == null) {
File tempDir = repository;
if (tempDir == null) {
tempDir = new File((""));
}
String tempFileName = format("upload_%s_%", UID, getUniqueId());
tempFile = new File(tempDir, tempFileName);
}
return tempFile;
}
也就是基于设置的repository 目录的临时文件
从风险角度来考虑
1. 复制到临时文件upload_UID_UniqueID.tmp
2. 可以删除任意有删除权限的文件,问题的风险性并不高
CVE的描述
CVE所提示的风险感觉定义不清楚
Apache Commons FileUpload DiskFileItem File Manipulation Remote Code Execution
DiskFileItem中注入执行代码,也无法在反序列化的时候被执行,这个和collection的反序列化的漏洞的风险是完全不同的,不清楚为何描述成可以操纵执行远端代码,是否是只要是反序列化的漏洞就是可以执行远端代码?
JDK6下的风险
JDK7以上在Java的file相关的基础类中都做了空字符的保护,这也是在针对java的string 和 c char的结束方式不一致,在Java中文件的操作中使用String这种char 数组,而C中的char 是以空字符为结束符,所以java操作的文件中很容易通过注入空字符来操作完全不同的文件
比如Java File file = new File("/test/\") 看起来再操作 \ 实际上在底层调用的(本质还是c读写文件)是在操作
在JDK7以后的版本File 里面会有一个判断是否有空字符的函数
final boolean isInvalid() {
if (status == null) {
status = (('\u0000') < 0) ?
: ;
}
return status == ;
}
而在很多关键操作中都会有isInValid 函数的判断,避免被注入空字符绕过风险,但是JDK6并没有进行防护,在JDK6是可以通过输入空字符串进行文件名控制。
1. 我们先看目录的空字符是否有机会注入,在readObject的代码中,我们看到了在判断目录的时候,对路径进行了空字符保护
if (repository != null) {
if (()) {
// Check path for nulls
if (().contains("\0")) {
throw new IOException(format(
"The repository [%s] contains a null character",
()));
}
} else {
throw new IOException(format(
"The repository [%s] is not a directory",
()));
}
}
2. 在文件名中我们看是否有机会注入空字符
String tempFileName = format("upload_%s_%", UID, getUniqueId());
UID, getUniqueId 都是可以构造的,但只能构造upload_为前缀的文件名
3. 通过相对路径构建任意文件名
构建 设置UID = /../../anyfile\0
只要有upload_为前缀的目录存在,通过相对路径,可以生成任意文件,同时最后注入空字符,结束文件名。这样就可以在服务器端生产任意文件
生成任意文件前提
1. JDK6 以下的版本
2. 必须在系统里存在upload_为前缀的目录存在
显然形成这样的攻击,前提条件是苛刻的
其他的风险
Dos攻击
因为使用了 DeferredFileOutputStream ,可以设置一个超大的
sizeThreshold(保存在内存里),设置一个系统里的超大文件
dfosFile在反序列化时复制dfosFile 到DefferredFileOutputStream导致JVM OOM
删除任意可删除的文件
设置dfosFile 为任意文件,因为反序列化后,复制完后会删除dfosFile,这样达到删除任意文件的目的
通过上述分析,我们可以看到上传文件反序列化的问题,并没有什么高危操作,而且有些攻击还必须有场景配合。
APACHE的官方态度
/jira/browse/FILEUPLOAD-279
Apache 目前给出几个方案
1. 不认为是问题
2. 去除的反序列化功能,这显然遭到了反对
3. 和collection解决方案一样在添加个系统参数的开关,如果打开的话,才能进行反序列化,就好像如果你打开了开关,就必须自己校验当从外部非可信域传入的时候
目前还无选择结果
反序列化JVM的不作为
目前JVM在反序列化的时候,JVM无法通过沙箱设置反序列化的类的黑白名单,只确保的反序列化类是在class loader 能初始化的。
对反序列化的保护,目前常见的解决方案扩展
ObjectInputStream,在resolveClass方法里进行类的黑白名单保护,但涉及到上层代码的改动。