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

反序列化漏洞最近一直不得安宁,先有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方法里进行类的黑白名单保护,但涉及到上层代码的改动。





你可能感兴趣的:(fileupload,反序列化)