Spring Cloud Config Server 任意檔案讀取分析
在 這裡 先生成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
0x03 漏洞分析
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 方法讀取我們傳入的檔案路徑,並且輸出。
0x04 漏洞修復
主要是針對了我們在之前在 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; }