Tomcat CVE-2017-12615 补丁bypass

2017-10-14  本文已影响0人  白里个白

分析

补丁原理

当补丁打完之后,我们传入文件的时候,程序做了这样的处理

protected File file(String name, boolean mustExist) {
    File file = new File(base, name);
    return validate(file, mustExist, absoluteBase);
}

调用了validate函数

protected File validate(File file, boolean mustExist, String absoluteBase) {

    if (!mustExist || file.exists() && file.canRead()) { // !mustExist = true,进入 if
        ...
        try {
            canPath = file.getCanonicalPath(); 
        } catch (IOException e) {

        }
        ...
        if ((absoluteBase.length() < absPath.length())
            && (absoluteBase.length() < canPath.length())) {
            ...
            // 判断处理后的路径和传入的路径参数是否相等,由于 canPath 把出入的路径参数的空格去掉了,导致canPath和之前的路径不同,return null
            if (!canPath.equals(absPath))
                return null;
        }
    } else {
        return null;
    }

也就是,如果我们传入jsp[空格] 这样的文件,就会使用file.getCanonicalPath()来进行格式规整,下面的条件就无法通过,所以无法创建文件。

突破

我们进入file.getCanonicalPath()函数

public String getCanonicalPath() throws IOException {
    if (isInvalid()) {
        throw new IOException("Invalid file path");
    }
    return fs.canonicalize(fs.resolve(this));
}

这里调用了fs的canonicalize函数,fs是对WinNTFileSystem对象的实例,我们进WinNTFileSystem看看,发现是Win32FileSystem的子类。我们主要看canonicalize函数,进入Win32FileSystem

public String canonicalize(String path) throws IOException {
        // If path is a drive letter only then skip canonicalization
        //用处不大
        int len = path.length();
        if ((len == 2) &&
            (isLetter(path.charAt(0))) &&
            (path.charAt(1) == ':')) {
            char c = path.charAt(0);
            if ((c >= 'A') && (c <= 'Z'))
                return path;
            return "" + ((char) (c-32)) + ':';
        } else if ((len == 3) &&
                   (isLetter(path.charAt(0))) &&
                   (path.charAt(1) == ':') &&
                   (path.charAt(2) == '\\')) {
            char c = path.charAt(0);
            if ((c >= 'A') && (c <= 'Z'))
                return path;
            return "" + ((char) (c-32)) + ':' + '\\';
        }
        //用处不大
        if (!useCanonCaches) {
            return canonicalize0(path);
        } else {
            String res = cache.get(path);
            if (res == null) {
                String dir = null;
                String resDir = null;
                if (useCanonPrefixCache) {
                    dir = parentOrNull(path);
                    if (dir != null) {
                        resDir = prefixCache.get(dir);
                        if (resDir != null) {
                            // Hit only in prefix cache; full path is canonical,
                            // but we need to get the canonical name of the file
                            // in this directory to get the appropriate capitalization
                            String filename = path.substring(1 + dir.length());
                            res = canonicalizeWithPrefix(resDir, filename);
                            cache.put(dir + File.separatorChar + filename, res);
                        }
                    }
                }
                if (res == null) {
                    res = canonicalize0(path);
                    cache.put(path, res);
                    if (useCanonPrefixCache && dir != null) {
                        resDir = parentOrNull(res);
                        if (resDir != null) {
                            File f = new File(res);
                            if (f.exists() && !f.isDirectory()) {
                                prefixCache.put(dir, resDir);
                            }
                        }
                    }
                }
            }
            return res;
        }
    }

我们主要看核心代码,我进行简单梳理:
首先,两个变量useCanonCachesuseCanonPrefixCache默认是等于ture的,这是CachesPrefixCache两个hashmap是否使用的配置变量,首先我们如果传入jsp[空格],那么cachehashmap里是不存在这个key对应的键值的。我们就进入了if里,那么parentOrNull函数在取得文件的路径之后,我们进入这一句resDir = prefixCache.get(dir);明显,prefixCache这个hashmap里是不存在我们的路径的,所以直接向下跳转调用res = canonicalize0(path);这个函数功能就是规范res,去掉了res中的空格。所以就回到上节说的为什么通不过了.
那么突破从何入手呢?首先我们传入正常的123.txt文件,这里都是十分正常通过,和上面一样,到了这里

 if (res == null) {
                    res = canonicalize0(path);
                    cache.put(path, res);
                    ......
if (f.exists() && !f.isDirectory()) {
                                prefixCache.put(dir, resDir);
                            }

程序将res作为path的键值存入了cache(重要),程序判断了文件是否存在,以及文件是否是路径格式,这里我们不满足文件存在的条件,也就没有任何动作。
第二遍我们传入123.txt[空格],这里为什么要加空格呢,因为要过这个条件:

String res = cache.get(path);
            if (res == null) {

如果path相同,cache中已经存在了这个path对应的键值,就不会进入if了。我们使用123.txt[空格]便过了这一点,之后,就正常进入程序运行,但是在后面,因为canonicalize0处理掉了res的空格,所以当实例化file时,file.exists()的返回值变为ture了,那么进入了prefixCache.put(dir, resDir);,那么prefixCache里便存在了dir。
第三次再次传入123.jsp[空格]时,正常走程序,当到了resDir = prefixCache.get(dir);的时候,这里由于第二次我们在prefixCache里传入了值,resDir就有值了,就会进入:

if (resDir != null) {
                            String filename = path.substring(1 + dir.length());
                            res = canonicalizeWithPrefix(resDir, filename);
                            cache.put(dir + File.separatorChar + filename, res);
                        }

这里是不会对文件整理的,就这样,我们绕过了之前的absoluteBase.length() < canPath.length(),文件就可以创建成功了

上一篇下一篇

猜你喜欢

热点阅读