Spring Cloud Config Server 任意文件读取分析

这里 先生成spring工程。

然后修改 pom.xml ,引入 spring cloud config 依赖。

org.springframework.cloud
spring-cloud-config-server
2.0.2.RELEASE

新建一个 ConfigServerApplication.java 文件,导入下面的代码

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;


@EnableConfigServer
@EnableAutoConfiguration
@SpringBootApplication

public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

配置 application.properties 文件,增加端口以及 giturl

server.port=8888
spring.cloud.config.server.git.uri=https://github.com/SukaraLin/awesome-cve-poc.git

0x03 漏洞分析

Spring Cloud Config是 Spirng Cloud 下用于分布式配置管理的组件,分为 Config-ServerConfig-Client 两个角色。 Config-Server 负责集中存储/管理配置文件, Config-Client 则可以从 Config-Server 提供的HTTP接口获取配置文件使用。首先看一下路由请求,代码在 spring-cloud-config-server-2.0.2.RELEASE.jar!/org/springframework/cloud/config/server/resource/ResourceController.class

@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 = this.getFilePath(request, name, profile, label);
return this.retrieve(name, profile, label, path, resolvePlaceholders);
}

也就是说我们可以通过请求 GET /{name}/{profile}/{label}/{path} 来获取配置文件,这里 name 为应仓库名称, profile 为应配置文件环境, label 为git分支名。实际测试中需要 label 为存在的分支名(一般git仓库都存在master分支),否则报错,name和profile可以为任意。所以我们构造如下payload即可命中这个 RequestMapping

http://127.0.0.1:8888/aaa/bbb/master/{payload}

这里在path打一个断点,单步跟入一下,我们发现 path 为我们传入的 payload ,且已经经过了一次 urldecode

跟进一下 retrieve 方法,位置在

org/springframework/cloud/config/server/resource/ResourceController.class:79

再跟进一下 findOne 方法,位置在

org/springframework/cloud/config/server/resource/GenericResourceRepository.class:31

这里我在 file.exists() && file.isReadable() 这里下一个断点,可以看到 locations 的值是一个临时文件夹

file:/var/folders/f0/pg_5gh954xl9r26dq3p0dxgc0000gn/T/config-repo-1558519048781287859/

local 的值就是我们传入的 payload

进入这个临时文件夹看看,这里我们配置的git目录的内容,被它拉取了一份到临时文件夹下。

最后return的时候,自然将路径拼接在一起。

然后返回到了 retrieve 方法中,调用了 StreamUtils.copyToString 方法读取我们传入的文件路径,并且输出。

0x04 漏洞修复

漏洞修复代码位置

主要是针对了我们在之前在 local 传入的 payload 位置进行了处理,处理方法是 isInvalidPathisInvalidEncodedPath

if (!isInvalidPath(local) && !isInvalidEncodedPath(local)) {
Resource file = this.resourceLoader.getResource(location)
.createRelative(local);
if (file.exists() && file.isReadable()) {
return file;
}

主要还是对 :/..WEB-INF 等关键字样进行检测。

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(":/")) {
...
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
if (logger.isWarnEnabled()) {
logger.warn("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]");
}
return true;
}
return false;
}