漏洞公告¶
https://pivotal.io/security/cve-2019-3799
漏洞复现¶
环境搭建: https://github.com/spring-cloud/spring-cloud-config#quick-start
GET /foo/default/master/..%252F..%252F..%252F..%252Fetc%252fpasswd HTTP/1.1
Host: localhost:8888
漏洞分析¶
Spring Cloud Config是Spirng Cloud下用于分布式配置管理的组件,分为Config-Server
和Config-Client
两个角色。 Config-Server
负责集中存储/管理配置文件,Config-Client
则可以从Config-Server
提供的HTTP接口获取配置文件使用。2019年4月16日,Pivotal官方发布安全通告,Spring Cloud Config Server 部分版本存在目录遍历漏洞,据此可以获取Server端服务器文件。
根据官方文档,可以通过如下请求GET /{name}/{profile}/{label}/{path}
来获取配置文件,name
,profile
和label
的含义与常规环境下的endpoint相同,而path
是指文件名。以官方示例为环境,我们请求 https://github.com/spring-cloud-samples/config-repo/blob/master/test.json 这个文件并以文本形式返回 ,则我们需要向Spring Cloud Config Server
发出如下请求:
GET http://127.0.0.1:8888/foo/label/master/test.json
根据请求格式可以在 org/springframework/cloud/config/server/resource/ResourceController.java:54
中找到对应的处理 @RequestMapping("/{name}/{profile}/{label}/**")
:
其中path
值即为payload:..%2F..%2F..%2F..%2Fetc%2fpasswd
跟入retrieve
在org/springframework/cloud/config/server/resource/ResourceController.java:104
:
synchronized String retrieve(ServletWebRequest request, String name, String profile,
String label, String path, boolean resolvePlaceholders) throws IOException {
name = resolveName(name);
label = resolveLabel(label);
Resource resource = this.resourceRepository.findOne(name, profile, label, path);
...
}
这里会根据前面所传条件获取到resource。文档中提到only the first one to match is returned
,所以继续跟入findOne
:
可以看到这里locations
的值为file:/tmp/config-repo-7168113927339570935/
,这是Config-Server
从后端拉取到配置文件时临时存放,正常情况下将会在该文件夹下进行文件的查找,比如test.json
:
不过我们传入的却是..%2F..%2F..%2F..%2Fetc%2fpasswd
,最终拼接出来的文件url即为:
返回后获取到的resource
即为/etc/passwd
,调用StreamUtils.copyToString(is, Charset.forName("UTF-8")
读取到文件内容:
漏洞补丁¶
https://github.com/spring-cloud/spring-cloud-config/commit/3632fc6f64e567286c42c5a2f1b8142bfde505c2
主要在获取到local后进行了判断:
if (!isInvalidPath(local) && !isInvalidEncodedPath(local)) {
Resource file = this.resourceLoader.getResource(location)
.createRelative(local);
if (file.exists() && file.isReadable()) {
return file;
}
}
isInvalidPath
用于检测其中是否含有:/
、..
、WEB-INF
等关键字样,isInvalidEncodedPath
中在进行编解码后仍是调用isInvalidPath
进行检测。