事件背景
Spring Cloud Config为分布式系统的外部配置提供客户端的服务端的支持。使用了它,开发人员就可以在一个中心仓库管理应用程序在所有环境中的外部配置。2020-02-26 Spring 收到漏洞报告, Spring Cloud Config Server 存在目录穿越漏洞。
时间线
2020-02-26 Spring 收到漏洞报告, Spring Cloud Config Server 存在目录穿越漏洞
2020-03-03 Spring Cloud Config Server 更新 2.1.7 版本
2020-03-04 Spring Cloud Config Server 更新 2.2.2 版本
2020-03-06 斗象应急响应团队分析出漏洞POC
漏洞挖掘
根据描述该漏洞为目录穿越 2.2.0-2.2.1,2.1.0-2.1.6 都受到影响, 所以这里直接下载2.2.1和2.2.2的源码包进行对比
https://github.com/spring-cloud/spring-cloud-config/releases
解压后直接使用idea Ctrl+D 进行对比
很多开源项目都会在test文件中写入本次更新涉及的漏洞点
比如这次 spring-cloud-config-2.2.2.RELEASE/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/environment/EnvironmentTests.java
显而易见的这个漏洞是用 (_)
来代替 /
进行目录穿越
搭建环境
这里启动 2.2.1环境, 使用idea 打开 (maven项目)\spring-cloud-config-2.2.1.RELEASE\spring-cloud-config-server
这里如果速度慢可以修改成阿里源,启动成功后访问
http://127.0.0.1:8888/foo/default/master/test.json
补丁对比
根据上一个目录穿越漏洞
Spring Cloud Config Server 路径穿越与任意文件读取漏洞分析 – 【CVE-2019-3799】 – 先知社区
https://xz.aliyun.com/t/4844
这里可以构造一个路径进行尝试(看看报错)
/foo/default/master/..(_)..(_)..(_)..(_)etc(_)passwd
效果不佳,从文中可知请求格式如下
GET /{name}/{profile}/{label}/{path}
继续看对应实现源码
org/springframework/cloud/config/server/resource/ResourceController.java
这里可以知道请求的格式,这里决定了POC的基本结构
继续对比可以可以看出, (_)
会在 [name
/label
] 时被处理成 /
再根据新增的测试文件
spring-cloud-config-2.2.2.RELEASE/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java
基本可以构造出来如下格式
由于label中可以解析(_)
根据提示 “Cannot clone or checkout repository” 以及 代码中的this.nativeRepository.setSearchLocations("file:./src/test/resources/test/local");
这里考虑可能是得采用本地配置文件方式
玩转Spring Cloud之配置中心(config server &config client) – 梦在旅途 – 博客园
https://www.cnblogs.com/zuowj/p/10432445.html
更改配置文件为
configserver.yml
info:
component: Config Server
spring:
application:
name: configserver
autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
profiles:
active: native
jmx:
default_domain: cloud.config.server
cloud:
config:
server:
#如下是本地文件配置
native:
search-locations: classpath:/configs #配置文件存放的目录
server:
port: 8888
management:
context_path: /admin
然后再resources目录下新建文件夹 configs
随便放个配置文件
configclient-dev.properties
--configclient-dev.properties内容:
demo-config-profile-env=dev-native
zuowenjun.site=http://www.zuowenjun.cn,http://zuowj.cnblogs.com--20190227
zuowenjun.skills=.net,java,html,js,css,sql,python,vb--20190227
zuowenjun.motto=Learning is endless; Opportunity is for the prepared mind;--20190227
访问 http://127.0.0.1:8888/configclient/dev
当native本地方式启动时, {label}无需指定(有用但是对这个漏洞没有影响), 所以这里可以采取{label}可以生效的方式git后端配置的请求格式进行访问
Spring Cloud Config Server 启动后, GIT仓库中的配置文件会被自动转换成当前项目的web api,若需访问查看远程配置数据可以参照以下的规则:
/{application}/{profile}[/{label}]
[/{label}]/{application}-{profile}{.yml|.properties|.json}
规则简单说明:{application}=配置消费方应用名称(即:config client的项目名,通俗讲:就是谁用这个配置就是谁的名字)
{profile}=配置环境(如:dev开发环境,test测试环境,prod生产环境)
{label}=仓库分支名(git或svn方式指定,native本地方式无需指定)
.yml|.properties|.json表示指定的响应返回格式,{}表示必需,[]表示可选,|表示或的关系
或者参考
https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.2.RELEASE/reference/html/#_placeholders_in_git_uri
https://www.cnblogs.com/zuowj/p/10432445.html
当配置文件中的 search-locations 修改为 file:///c:/
时,即可在在根目录进行文件读取
configserver.yml
info:
component: Config Server
spring:
application:
name: configserver
autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
jmx:
default_domain: cloud.config.server
profiles:
active: native
cloud:
config:
server:
native:
search-locations: file:///C:\TCC\Spring-cloud-config-server\spring-cloud-config-2.2.1.RELEASE\spring-cloud-config-server\src\main\resources\configs #配置文件存放的目录
server:
port: 8888
management:
context_path: /admin
断点分析
通过打断点,我们可以更清晰的看到漏洞的成因
首先根据路由格式,进入 org.springframework.cloud.config.server.resource.ResourceController#retrieve(java.lang.String, java.lang.String, java.lang.String, org.springframework.web.context.request.ServletWebRequest, boolean)
函数中
其中参数分别为我们传入的
name = blah
profile = local
label = 马赛克
path = Windows/win.ini
然后进入org.springframework.cloud.config.server.resource.GenericResourceRepository#findOne
函数
这里会对 Win[-{label}].ini
也就是 win-label.ini win.ini
在对应目录搜索,
也就是 {search-locations} + {label}
即
file:///C:\TCC\Spring-cloud-config-server\spring-cloud-config-2.2.1.RELEASE\spring-cloud-config-server\src\main\resources\configs/../../../../../../../../../../../../../../../../../../../../../../../../../../../../..//
也就是进入了C盘根目录, 即可读取文件
无后缀文件读取
但是因为org.springframework.cloud.config.server.resource.ResourceController#retrieve(org.springframework.web.context.request.ServletWebRequest, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)
中存在对于后缀的匹配导致没有后缀的文件会读取失败
46
对应ascii码的.
当没有获取到 “.” 的时候返回 null 导致后续 null.toLowerCase() 报错异常退出
但是在windows中.当文件名后面加空格或者点号.
也可以正确处理
Naming Files, Paths, and Namespaces – Win32 apps | Microsoft Docs
https://docs.microsoft.com/zh-cn/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN
Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not. However, it is acceptable to specify a period as the first character of a name. For example, ".temp".
所以在Windows中,我们可以使用文件名加点.
的方式进行无后缀文件读取
漏洞产生根本原因
org.springframework.cloud.config.server.environment.NativeEnvironmentRepository#getLocations
这里产生了直接拼接{label}的行为
所以这里双重url编码/
为%252f
还是可以用,因为最后拼接成的路径是URL类,支持%2f
使用 %252f
请求
Spring修复方式
修复点在spring-cloud-config-2.2.2.RELEASE/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java
org.springframework.cloud.config.server.resource.GenericResourceRepository#findOne
函数加入了对路径的判断
org.springframework.cloud.config.server.resource.GenericResourceRepository#isInvalidEncodedLocation
会先对路径进行url解码
org.springframework.cloud.config.server.resource.GenericResourceRepository#isInvalidLocation
然后检测是否包含 ..
检测方法
Windows 可以检测 C:/Windows/win.ini
Linux 可以检测 /etc/resolv.conf
(这里测试Windows上file协议无法访问其他盘,如果Spring Cloud Config Server 搜索路径指定在 classpath,可以尝试读取META-INF/下面的文件,这里是个检测难点)
作者:斗象能力中心 TCC – 小胖虎