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
 
 
基本可以构造出来如下格式
 
 (_) 
  
 
  根据提示 “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=MSDNDo 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-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 然后检测是否包含  ..