在 这里 先生成spring工程。
然后修改 pom.xml ,引入 spring cloud config 依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> <version>2.0.2.RELEASE</version> </dependency>
新建一个 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
Spring Cloud Config是 Spirng Cloud 下用于分布式配置管理的组件,分为 Config-Server
和 Config-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 方法读取我们传入的文件路径,并且输出。
漏洞修复代码位置
主要是针对了我们在之前在 local 传入的 payload 位置进行了处理,处理方法是 isInvalidPath 和 isInvalidEncodedPath
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; }