spring-cloud-config目录穿越漏洞分析
2020-04-16 10:05:30 Author: xz.aliyun.com(查看原文) 阅读量:356 收藏

最近打算分析一下spring相关的漏洞,就以spring-cloud-config产生的目录穿越漏洞为引,进行学习,另外,为了更好的提高自己的能力,我们对漏洞只提前去了解补丁和影响的版本,不拿poc去看调用栈的流程,从补丁分析poc的编写。

CVE-2019-3799漏洞补丁

补丁位置:https://github.com/spring-cloud/spring-cloud-config/commit/3632fc6f64e567286c42c5a2f1b8142bfde505c2

根据补丁可以看到主要增加了路径的检测,一个是

private boolean isInvalidEncodedPath(String path) {
        if (path.contains("%")) {
            try {
                // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
                String decodedPath = URLDecoder.decode(path, "UTF-8");
                if (isInvalidPath(decodedPath)) {
                    return true;
                }
                decodedPath = processPath(decodedPath);
                if (isInvalidPath(decodedPath)) {
                    return true;
                }
            }
            catch (IllegalArgumentException | UnsupportedEncodingException ex) {
                // Should never happen...
            }
        }
        return false;
    }

这个我们可以看到主要是url解码的功能,看到这里会想到难道url编码可以绕过限制进行文件读取?如果是这样的话那么在读文件时要么有解码操作,要么可能是file协议等。

第二个函数我们可以看到主要是限制了一些路径出现的字符,防止我们目录穿越已经读敏感文件的

protected boolean isInvalidPath(String path) {
        if (path.contains("WEB-INF") || path.contains("META-INF")) {
            if (logger.isWarnEnabled()) {
                logger.warn("Path with \"WEB-INF\" or \"META-INF\": [" + path + "]");
            }
            return true;
        }
        if (path.contains(":/")) {
            String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path);
            if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Path represents URL or has \"url:\" prefix: [" + path + "]");
                }
                return true;
            }
        }
        if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
            if (logger.isWarnEnabled()) {
                logger.warn("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]");
            }
            return true;
        }
        return false;
    }

CVE-2019-3799具体分析

如果我们对该组件不熟悉的话,可以通过回溯进行找漏洞利用点。

通过看补丁我们知道漏洞产生点在findOne方法,全局找一下

并且通过看findOne的代码我们可以知道,path可控是比较重要的,重点跟一下可控处

找到

synchronized String retrieve(String name, String profile, String label, String path,
            boolean resolvePlaceholders) throws IOException {
        if (name != null && name.contains("(_)")) {
            // "(_)" is uncommon in a git repo name, but "/" cannot be matched
            // by Spring MVC
            name = name.replace("(_)", "/");
        }
        if (label != null && label.contains("(_)")) {
            // "(_)" is uncommon in a git branch name, but "/" cannot be matched
            // by Spring MVC
            label = label.replace("(_)", "/");
        }

        // ensure InputStream will be closed to prevent file locks on Windows
        try (InputStream is = this.resourceRepository.findOne(name, profile, label, path)
                .getInputStream()) {
            String text = StreamUtils.copyToString(is, Charset.forName("UTF-8"));
            if (resolvePlaceholders) {
                Environment environment = this.environmentRepository.findOne(name,
                        profile, label);
                text = resolvePlaceholders(prepareEnvironment(environment), text);
            }
            return text;
        }
    }

再往上找就能找到

@RequestMapping("/{name}/{profile}/{label}/**")
    public String retrieve(@PathVariable String name, @PathVariable String profile,
            @PathVariable String label, HttpServletRequest request,
            @RequestParam(defaultValue = "true") boolean resolvePlaceholders)
            throws IOException {
        String path = getFilePath(request, name, profile, label);
        return retrieve(name, profile, label, path, resolvePlaceholders);
    }

然后这里可以看到path是我们完全可控的,也就是**

那么我们根据路由构建个请求

http://127.0.0.1:8888/aaaa/aaaa/master/README.md

跟进一下看到

进行了路径的拼接,因为我们一开始并不熟悉该组件的功能,看到这里猜测是将远程管理项目的下载到本地,然后利用file协议来进行读取文件,这里也解释了为什么写过滤的时候会进行url解码。

因为浏览器会进行一次url解码,然后在getInputStream中openConnection会解码一次

那么我们就可以构造poc

http://127.0.0.1:8888/aaaa/aaaa/master/..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd

CVE-2020-5405漏洞补丁

补丁位置:https://github.com/spring-cloud/spring-cloud-config/commit/651f458919c40ef9a5e93e7d76bf98575910fad0

通过补丁我们可以知道,第一次补丁只检测了path,第二个补丁检测了整个路径,那么问题还应该出在了这里.

CVE-2020-5405漏洞分析

其实往前看有个替换的操作,很显眼

name和label的(_)会被替换成/,看到这里,我尝试构造poc

http://127.0.0.1:8888/aaaa/aaaa/%2e%2e%28%5f%29%2e%2e%28%5f%29%2e%2e%28%5f%29%2e%2e%28%5f%29%2e%2e%28%5f%29%2e%2e%28%5f%29%2e%2e%28%5f%29%2e%2e%28%5f%29%65%74%63/passwd

我们让label为..(_)..(_)..(_)..(_)..(_)..(_)..(_)..(_)etc,path为passwd,这样拼接完应该是file:/xxx/xxx/xxx/../../../../../etc/passwd

但是如果label不为master的话,代码逻辑就会先去checkout,然后就异常了

这里面会导致抛出异常,具体抛出异常的点在哪呢,在org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository

在CVE-2019-3799中,我们使用的配置是常用的spring.cloud.config.server.git.uri,它会选择使用MultipleJGitEnvironmentRepository.class的getLocations,然后就会走到checkout,抛出异常了,那么我们怎么去不走checkout逻辑呢。

通过翻文档https://zq99299.github.io/note-book/spring-cloud-tutorial/config/002.html#%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6%E5%90%8E%E7%AB%AF%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F

得知这个配置是让它从本地进行加载,而不是用git,通过在application.properties配置

spring.profiles.active=native
spring.cloud.config.server.native.search-locations=file:/Users/p0desta/Desktop

其实通过补丁我们也可以发现端倪

配置后重新跟进调试

然后就在/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.class@getLocations中对路径进行了处理,大致就是对路径进行拼接,然后存在ouput数组中,返回一个新的Locations对象

然后后面就是跟CVE-2019-3799一样,直接读文件了。


文章来源: http://xz.aliyun.com/t/7558
如有侵权请联系:admin#unsafe.sh