CVE-2016-1000031 Apache Commons FileUpload 反序列化漏洞深入分析

来源:互联网 发布:ubuntu 界面 编辑:程序博客网 时间:2024/04/30 12:50

反序列化漏洞最近一直不得安宁,先有Apache Commons Collections通过反序列化实现远程代码执行,再有Spring RMI 反序列化漏洞,最新又有了common upload file的反序列化漏洞CVE-2016-1000031(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-1000031)

漏洞原因

先来看DiskFileItem.java 代码
    /**     * 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        in.defaultReadObject();        /* 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 (repository.isDirectory()) {                // Check path for nulls                if (repository.getPath().contains("\0")) {                    throw new IOException(format(                            "The repository [%s] contains a null character",                            repository.getPath()));                }            } else {                throw new IOException(format(                        "The repository [%s] is not a directory",                        repository.getAbsolutePath()));            }        }        OutputStream output = getOutputStream();        if (cachedContent != null) {            output.write(cachedContent);        } else {            FileInputStream input = new FileInputStream(dfosFile);            IOUtils.copy(input, output);            dfosFile.delete();            dfosFile = null;        }        output.close();        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(System.getProperty("java.io.tmpdir"));            }            String tempFileName = format("upload_%s_%s.tmp", 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/test.txt\0.jsp") 看起来再操作 test.txt\0.jsp 实际上在底层调用的(本质还是c读写文件)是在操作test.txt
在JDK7以后的版本File 里面会有一个判断是否有空字符的函数
final boolean isInvalid() {        if (status == null) {            status = (this.path.indexOf('\u0000') < 0) ? PathStatus.CHECKED                                                       : PathStatus.INVALID;        }        return status == PathStatus.INVALID;    }

而在很多关键操作中都会有isInValid 函数的判断,避免被注入空字符绕过风险,但是JDK6并没有进行防护,在JDK6是可以通过输入空字符串进行文件名控制。
1. 我们先看目录的空字符是否有机会注入,在readObject的代码中,我们看到了在判断目录的时候,对路径进行了空字符保护
 if (repository != null) {            if (repository.isDirectory()) {                // Check path for nulls                if (repository.getPath().contains("\0")) {                    throw new IOException(format(                            "The repository [%s] contains a null character",                            repository.getPath()));                }            } else {                throw new IOException(format(                        "The repository [%s] is not a directory",                        repository.getAbsolutePath()));            }        }

2. 在文件名中我们看是否有机会注入空字符
String tempFileName = format("upload_%s_%s.tmp", 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的官方态度

https://issues.apache.org/jira/browse/FILEUPLOAD-279
Apache 目前给出几个方案
1. 不认为是问题
2. 去除DiskFileItem.java的反序列化功能,这显然遭到了反对
3. 和collection解决方案一样在添加个系统参数的开关,如果打开的话,才能进行反序列化,就好像如果你打开了开关,就必须自己校验当从外部非可信域传入的时候

目前还无选择结果

反序列化JVM的不作为

目前JVM在反序列化的时候,JVM无法通过沙箱设置反序列化的类的黑白名单,只确保的反序列化类是在class loader 能初始化的。
对反序列化的保护,目前常见的解决方案扩展ObjectInputStream,在resolveClass方法里进行类的黑白名单保护,但涉及到上层代码的改动。





0 0
原创粉丝点击