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的官方態度

Apache 目前給出幾個方案
1. 不認爲是問題
2. 去除DiskFileItem.java的反序列化功能,這顯然遭到了反對
3. 和collection解決方案一樣在添加個系統參數的開關,如果打開的話,才能進行反序列化,就好像如果你打開了開關,就必須自己校驗當從外部非可信域傳入的時候

目前還無選擇結果

反序列化JVM的不作爲

目前JVM在反序列化的時候,JVM無法通過沙箱設置反序列化的類的黑白名單,只確保的反序列化類是在class loader 能初始化的。
對反序列化的保護,目前常見的解決方案擴展ObjectInputStream,在resolveClass方法裏進行類的黑白名單保護,但涉及到上層代碼的改動。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章