spring-cloud-config目录穿越漏洞分析

2020-04-16 约 362 字 预计阅读 2 分钟

声明:本文 【spring-cloud-config目录穿越漏洞分析】 由作者 p0desta 于 2020-04-16 09:05:30 首发 先知社区 曾经 浏览数 110 次

感谢 p0desta 的辛苦付出!

最近打算分析一下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一样,直接读文件了。

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now