漏洞分析 | Spring Framework路径遍历漏洞(CVE-2024-38816)

news/2024/11/8 7:36:58 标签: spring, 安全威胁分析, 网络安全

漏洞概述

VMware Spring Framework是美国威睿(VMware)公司的一套开源的Java、JavaEE应用程序框架。该框架可帮助开发人员构建高质量的应用。

近期,网宿安全演武实验室监测到Spring Framework在特定条件下,存在目录遍历漏洞(网宿评分:高危、CVSS 3.1 评分:7.5):

当同时满足使用 RouterFunctions 和 FileSystemResource 来处理和提供静态文件时,攻击者可构造恶意请求遍历读取系统上的文件。

目前该漏洞POC状态已在互联网公开,建议客户尽快做好自查及防护。

受影响版本

Spring Framework 5.3.0 - 5.3.39

Spring Framework 6.0.0 - 6.0.23

Spring Framework 6.1.0 - 6.1.12

其他更旧或者官方已不支持的版本

漏洞分析

根据漏洞描述(https://spring.io/security/cve-2024-38816)可知,关键变更在于如何处理静态资源路径。

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 应用程序提供静态内容的访问。

以webmvc --> org.springframework.web.servlet.function.PathResourceLookupFunction为例,展开分析。

先是动态处理了资源请求,确保只返回有效并且可访问的资源。

@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);
        }
}

接着对路径字符串进行规范化处理,确保返回的路径格式是有效的。

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 ? "/" : "");
}

最后从安全角度,确保路径不指向敏感目录,并且避免出现路径穿越的情况。

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("../");
}

简单阐明以后,不难发现上述代码做了敏感目录检查、url检查、路径穿越检查等操作,暂时没发现可疑点,所以我们需要进一步跟进

org.springframework.web.servlet.function.PathResourceLookupFunction#isInvalidPath()

查看一下它检查相对路径时,StringUtils.cleanPath()做了哪些操作。

public static String cleanPath(String path) {
        if (!hasLength(path)) {
                return path;
        }
 
        String normalizedPath;
        // Optimize when there is no backslash
        if (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 do
        if (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 number
        Deque<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 - shortcut
        if (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 prefix
        return prefix.isEmpty() ? joined : prefix + joined;
}

这个方法主要对用户输入路径做了规范化处理,具体包括长度检查、不同操作系统下的路径分隔符处理等。看起来也做了严格的处理,但这一步存在问题。

String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);

具体来说,它是允许空元素存在的,假设路径字符串形如:

String pathToUse = "/static///../../Windows/win.ini";

那么调用 delimitedListToStringArray 方法以后,pathArray即为

["static", "", "", "..", "..", "Windows", "win.ini"]

而pathElements即为

再来看这一串:String pathToUse = "/static/../../Windows/win.ini";

显然,pathArray中存在空元素会影响上级目录的处理,导致返回不同的结果,即存在安全隐患。

漏洞复现

实现目录穿越需要用到"../",结合上述分析,可通过这种方式实现。

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("../");
}
}

但还需要结合上下文,继续构造payload。首先路径以斜杠开头时,StringUtils.cleanPath()方法会去掉路径的第⼀个斜杠。

if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
    prefix = prefix + FOLDER_SEPARATOR;
    pathToUse = pathToUse.substring(1);
}

那就需要多写一条"/",构造"///../"跳一级目录。

而在最初的org.springframework.web.servlet.function.PathResourceLookupFunction#apply()中,对路径做了规范化处理,即去掉连续的"/"

pathContainer = this.pattern.extractPathWithinPattern(pathContainer);
String path = processPath(pathContainer.value());

所以需要将多余的"/"变为"\",再借助StringUtils.cleanPath()方法重新转换回来。

normalizedPath = replace(normalizedPath, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);

修复方案

目前官方已有可更新版本,建议受影响用户升级至最新版本:

https://github.com/spring-projects/spring-framework/tags

产品支持

网宿全站防护-WAF已支持对该漏洞利用攻击的防护,并持续挖掘分析其他变种攻击方式和各类组件漏洞,第一时间上线防护规则,缩短防护“空窗期”。


http://www.niftyadmin.cn/n/5743520.html

相关文章

【SQL实验】高级查询(难点.三)含附加数据库操作

完整代码在文章末尾【代码是自己的解答&#xff0c;并非标准答案&#xff0c;也有可能写错&#xff0c;文中可能会有不准确或待完善之处&#xff0c;恳请各位读者不吝批评指正&#xff0c;共同促进学习交流】 将素材中的“学生管理”数据库附加到SQL SERVER中&#xff0c;完成以…

简单了解一下 TypeScript 的泛型

在 TypeScript (TS) 中&#xff0c;泛型是一个强大且灵活的工具&#xff0c;用于编写具有更高可复用性和类型安全性的代码。泛型允许我们在声明时将类型作为参数传入&#xff0c;使函数、接口和类能在不同的数据类型下复用&#xff0c;而无需重新编写逻辑。 1. 泛型的基本语法…

论文《基于柔顺控制的智能神经导航手术机器人系统设计》文献阅读分析报告

论文报告&#xff1a;基于卷积神经网络的手术机器人控制系统设计 摘要 本研究针对机器人辅助微创手术中定向障碍和缺乏导航信息的问题&#xff0c;设计了一种智能控制导航手术机器人系统。该系统采用可靠和安全的定位技术、7自由度机械臂以及避免关节角度限制的逆运动学控制策…

Android CCodec Codec2 (二十)C2Buffer与Codec2Buffer

在阅读Codec2框架代码时&#xff0c;我们可能会发现好几个名称中都带有“buffer”的类&#xff0c;如MediaCodecBuffer、ABuffer、CCodecBuffers、Codec2Buffer以及C2Buffer。它们分别是什么&#xff1f;各自承担着什么功能&#xff1f;它们之间有何联系&#xff1f;本文将围绕…

macos中安装和设置ninja

1、在安装ninja的过程中需要先安装re2c(github地址&#xff1a;https://github.com/skvadrik/re2c): git clone https://github.com/skvadrik/re2c.git&#xff08;也可直接下载最新的release压缩包&#xff0c;并解压。下载地址&#xff1a;https://github.com/skvadrik/re2c…

[论文阅读]Secure IP Address Allocation at Cloud Scale

Secure IP Address Allocation at Cloud Scale http://arxiv.org/abs/2210.14999 NDSS - Network and Distributed Systems Security Symposium (2025) 随着云计算和云基础设施的广泛应用&#xff0c;云服务提供商必须管理大量的网络资源&#xff0c;其中IP地址分配是重要的…

【Linux】Linux下查看cpu信息指令(top/mpstat/iostat/pidstat)说明

top命令 top(1) - Linux manual page (man7.org) top查看总的CPU利用率 us: 用户空间消耗的CPU资源占比&#xff0c;进程在用户态执行函数调用&#xff0c;编解码消耗的都是us sy: 内核空间消耗的CPU资源占比&#xff0c;进程调用系统调用达到内核后会增加sy的消耗 ni&…

浅谈语言模型推理框架 vLLM 0.6.0性能优化

在此前的大模型技术实践中&#xff0c;我们介绍了加速并行框架Accelerate、DeepSpeed及Megatron-LM。得益于这些框架的助力&#xff0c;大模型的分布式训练得以化繁为简。 然而&#xff0c;企业又该如何将训练完成的模型实际应用部署&#xff0c;持续优化服务吞吐性能&#xf…