品牌網(wǎng)站建設(shè)h合肥銷售
漏洞概述
VMware Spring Framework是美國(guó)威睿(VMware)公司的一套開源的Java、JavaEE應(yīng)用程序框架。該框架可幫助開發(fā)人員構(gòu)建高質(zhì)量的應(yīng)用。
近期,監(jiān)測(cè)到Spring Framework在特定條件下,存在目錄遍歷漏洞(網(wǎng)宿評(píng)分:高危、CVSS 3.1 評(píng)分:7.5):
當(dāng)同時(shí)滿足使用 RouterFunctions 和 FileSystemResource 來(lái)處理和提供靜態(tài)文件時(shí),攻擊者可構(gòu)造惡意請(qǐng)求遍歷讀取系統(tǒng)上的文件。
目前該漏洞POC狀態(tài)已在互聯(lián)網(wǎng)公開,建議客戶盡快做好自查及防護(hù)。
受影響版本
Spring Framework 5.3.0 - 5.3.39
Spring Framework 6.0.0 - 6.0.23
Spring Framework 6.1.0 - 6.1.12
其他更舊或者官方已不支持的版本
漏洞分析
根據(jù)漏洞描述(https://spring.io/security/cve-2024-38816)可知,關(guān)鍵變更在于如何處理靜態(tài)資源路徑。
https://github.com/spring-projects/spring-framework/commit/d86bf8b2056429edf5494456cffcb2b243331c49#diff-25869a3e3b3d4960cb59b02235d71d192fdc4e02ef81530dd6a660802d4f8707R4
這里改了兩處,分別是:
webflux --> org.springframework.web.reactive.function.server.PathResourceLookupFunction
webmvc --> org.springframework.web.servlet.function.PathResourceLookupFunction
它們都旨在為 Web 應(yīng)用程序提供靜態(tài)內(nèi)容的訪問。
以webmvc --> org.springframework.web.servlet.function.PathResourceLookupFunction為例,展開分析。
先是動(dòng)態(tài)處理了資源請(qǐng)求,確保只返回有效并且可訪問的資源。
@Override
public Optional<Resource> apply(ServerRequest request) {PathContainer pathContainer = request.requestPath().pathWithinApplication();if (!this.pattern.matches(pathContainer)) {return Optional.empty();}pathContainer = this.pattern.extractPathWithinPattern(pathContainer);String path = processPath(pathContainer.value());if (path.contains("%")) {path = StringUtils.uriDecode(path, StandardCharsets.UTF_8);}if (!StringUtils.hasLength(path) || isInvalidPath(path)) {return Optional.empty();}try {Resource resource = this.location.createRelative(path);if (resource.isReadable() && isResourceUnderLocation(resource)) {return Optional.of(resource);}else {return Optional.empty();}}catch (IOException ex) {throw new UncheckedIOException(ex);}
}
接著對(duì)路徑字符串進(jìn)行規(guī)范化處理,確保返回的路徑格式是有效的。
private String processPath(String path) {boolean slash = false;for (int i = 0; i < path.length(); i++) {if (path.charAt(i) == '/') {slash = true;}else if (path.charAt(i) > ' ' && path.charAt(i) != 127) {if (i == 0 || (i == 1 && slash)) {return path;}path = slash ? "/" + path.substring(i) : path.substring(i);return path;}}return (slash ? "/" : "");
}
最后從安全角度,確保路徑不指向敏感目錄,并且避免出現(xiàn)路徑穿越的情況。
private boolean isInvalidPath(String path) {if (path.contains("WEB-INF") || path.contains("META-INF")) {return true;}if (path.contains(":/")) {String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path);if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {return true;}}return path.contains("..") && StringUtils.cleanPath(path).contains("../");
}
簡(jiǎn)單闡明以后,不難發(fā)現(xiàn)上述代碼做了敏感目錄檢查、url檢查、路徑穿越檢查等操作,暫時(shí)沒發(fā)現(xiàn)可疑點(diǎn),所以我們需要進(jìn)一步跟進(jìn)
org.springframework.web.servlet.function.PathResourceLookupFunction#isInvalidPath()
查看一下它檢查相對(duì)路徑時(shí),StringUtils.cleanPath()做了哪些操作。
public static String cleanPath(String path) {if (!hasLength(path)) {return path;}String normalizedPath;// Optimize when there is no backslashif (path.indexOf('\') != -1) {normalizedPath = replace(path, DOUBLE_BACKSLASHES, FOLDER_SEPARATOR);normalizedPath = replace(normalizedPath, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);}else {normalizedPath = path;}String pathToUse = normalizedPath;// Shortcut if there is no work to doif (pathToUse.indexOf('.') == -1) {return pathToUse;}// Strip prefix from path to analyze, to not treat it as part of the// first path element. This is necessary to correctly parse paths like// "file:core/../core/io/Resource.class", where the ".." should just// strip the first "core" directory while keeping the "file:" prefix.int prefixIndex = pathToUse.indexOf(':');String prefix = "";if (prefixIndex != -1) {prefix = pathToUse.substring(0, prefixIndex + 1);if (prefix.contains(FOLDER_SEPARATOR)) {prefix = "";}else {pathToUse = pathToUse.substring(prefixIndex + 1);}}if (pathToUse.startsWith(FOLDER_SEPARATOR)) {prefix = prefix + FOLDER_SEPARATOR;pathToUse = pathToUse.substring(1);}String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);// we never require more elements than pathArray and in the common case the same numberDeque<String> pathElements = new ArrayDeque<>(pathArray.length);int tops = 0;for (int i = pathArray.length - 1; i >= 0; i--) {String element = pathArray[i];if (CURRENT_PATH.equals(element)) {// Points to current directory - drop it.}else if (TOP_PATH.equals(element)) {// Registering top path found.tops++;}else {if (tops > 0) {// Merging path element with element corresponding to top path.tops--;}else {// Normal path element found.pathElements.addFirst(element);}}}// All path elements stayed the same - shortcutif (pathArray.length == pathElements.size()) {return normalizedPath;}// Remaining top paths need to be retained.for (int i = 0; i < tops; i++) {pathElements.addFirst(TOP_PATH);}// If nothing else left, at least explicitly point to current path.if (pathElements.size() == 1 && pathElements.getLast().isEmpty() && !prefix.endsWith(FOLDER_SEPARATOR)) {pathElements.addFirst(CURRENT_PATH);}final String joined = collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);// avoid string concatenation with empty prefixreturn prefix.isEmpty() ? joined : prefix + joined;
}
這個(gè)方法主要對(duì)用戶輸入路徑做了規(guī)范化處理,具體包括長(zhǎng)度檢查、不同操作系統(tǒng)下的路徑分隔符處理等。看起來(lái)也做了嚴(yán)格的處理,但這一步存在問題。
String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
具體來(lái)說(shuō),它是允許空元素存在的,假設(shè)路徑字符串形如:
String pathToUse = “/static///…/…/Windows/win.ini”;
那么調(diào)用 delimitedListToStringArray 方法以后,pathArray即為
[“static”, “”, “”, “…”, “…”, “Windows”, “win.ini”]
而pathElements即為
再來(lái)看這一串:String pathToUse = “/static/…/…/Windows/win.ini”;
顯然,pathArray中存在空元素會(huì)影響上級(jí)目錄的處理,導(dǎo)致返回不同的結(jié)果,即存在安全隱患。
漏洞復(fù)現(xiàn)
實(shí)現(xiàn)目錄穿越需要用到"…/",結(jié)合上述分析,可通過這種方式實(shí)現(xiàn)。
package org.example.demo;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;public class test {
public static void main(String[] args) {
String path = “/static///…/…/Windows/win.ini”;
System.out.println(isInvalidPath(path));
}private static boolean isInvalidPath(String path) {
if (path.contains(“WEB-INF”) || path.contains(“META-INF”)) {
return true;
}
if (path.contains(“😕”)) {
String relativePath = (path.charAt(0) == ‘/’ path.substring(1) : path);
if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith(“url:”)) {
return true;
}
}
return path.contains(“…”) && StringUtils.cleanPath(path).contains(“…/”);
}
}
但還需要結(jié)合上下文,繼續(xù)構(gòu)造payload。首先路徑以斜杠開頭時(shí),StringUtils.cleanPath()方法會(huì)去掉路徑的第個(gè)斜杠。
if (pathToUse.startsWith(FOLDER_SEPARATOR)) {prefix = prefix + FOLDER_SEPARATOR;pathToUse = pathToUse.substring(1);
}
那就需要多寫一條"/“,構(gòu)造”///…/"跳一級(jí)目錄。
而在最初的org.springframework.web.servlet.function.PathResourceLookupFunction#apply()中,對(duì)路徑做了規(guī)范化處理,即去掉連續(xù)的"/"
pathContainer = this.pattern.extractPathWithinPattern(pathContainer);
String path = processPath(pathContainer.value());
所以需要將多余的"/“變?yōu)椤?#34;,再借助StringUtils.cleanPath()方法重新轉(zhuǎn)換回來(lái)。
normalizedPath = replace(normalizedPath, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
修復(fù)方案
目前官方已有可更新版本,建議受影響用戶升級(jí)至最新版本:
https://github.com/spring-projects/spring-framework/tags
產(chǎn)品支持
網(wǎng)宿全站防護(hù)-WAF已支持對(duì)該漏洞利用攻擊的防護(hù),并持續(xù)挖掘分析其他變種攻擊方式和各類組件漏洞,第一時(shí)間上線防護(hù)規(guī)則,縮短防護(hù)“空窗期”。