Traversal with Spring MVC on Windows复现分析【CVE-2018-1271】

漏洞信息

Spring Framework versions 5.0 to 5.0.4, 4.3 to 4.3.14, and older unsupported versions allow applications to configure Spring MVC to serve static resources (e.g. CSS, JS, images). When static resources are served from a file system on Windows (as opposed to the classpath, or the ServletContext), a malicious user can send a request using a specially crafted URL that can lead a directory traversal attack.

触发这个漏洞的条件有三个:

  1. 要使用file协议打开资源文件目录
  2. Windows平台
  3. 不能使用Tomcat或者wildfy等中间件(本环境演示使用的是jetty服务器)

环境搭建

  1. 修改配置文件org.springframework.samples.mvc.config.WebMvcConfig中的resources目录,

    1
    2
    3
       public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**").addResourceLocations("file:///C:/static/","/resources/");
    }

    其中的sourceLocations中的目录地址是根据自己的实际情况进行定义。我的项目是桌面上,所以我将sourceLocations目录地址指向C:/static/(这个没有明确地要求,根据自己的实际环境设置即可)。

    C/static/下存在123.txt的文件,内容是456

  2. 由于漏洞的环境要求是要求jetty服务器,我们需要通过mvn jetty:run这种方式启动,通过IDEA同样可以完成这样的功能。利用IDEA jetty插件就可以完成,具体的配置方法可以参考文章idea jetty插件。我的配置如下:

  3. 如果能够成功启动,没有报错。访问localhost:8080,出现如下的界面说明环境搭建成功。

漏洞复现

由于我们已经在WebMvcConfig中已经设置了resources所对应的本地路径。当我们访问resources路径下的文件时访问实际上是C:/static/下的文件。所以如果我们访问http://127.0.0.1:8080/spring-mvc-showcase/resources/123.txt实际上访问就是static目录下的123.txt。如下:

但是这样也就意味着存在目录穿越的漏洞。如:

成功地读取了windows/win.ini文件。

漏洞分析

首先需要说明的一个问题是,为什么不能使用Tomcat或者是wildfy服务器,是因为这些服务器是无法识别..\。像我们POC中出现了..%5c/这种路径,Tomcat等服务器就会出现404的错误。但是Jetty却可以识别这种路径,这也就是为什么这个漏洞的触发要求是Jetty服务器的原因。

接下来就对漏洞进行详细地分析。在org.springframework.web.servlet.resource.ResourceHttpRequestHandler:getResource()地方下断点进行分析。此时的参数信息如下:

request中保存的路劲是/spring-mvc-showcase/resources/static/..%5c/..%5c/windows/win.ini。经过request.getAttribute()函数之后,对其中的%5c进行了解码,path的值是static/..\/..\/windows/win.ini

之后运行到Resource resource = resolveChain.resolveResource(request, path, getLocations())。如下所示:

其中path就是我们上一步得到的static/..\/..\/windows/win.ini,getLocations()就是之前在配置文件中配置的路径C:/static/

之后通过层层调用,最终程序会运行到org.springframework.web.servlet.resource.PathResourceResolver:getResource()中:

通过Resource resource = location.createRelative(resourcePath);拼接得到访问文件的绝对路径,file:/C:/static/static/..%5C/..%5C/windows/win.ini

进一步跟踪,进入到org.springframework.core.io.AbstractFileResolvingResource:exists()中,

由于此时的url是file:/C:/static/static/..%5C/..%5C/windows/win.ini。通过了ResourceUtils.isFileURL(url)的判断,之后成功地读取了文件内容。

漏洞修复

我们修改pom.xml中的org.springframework-version为漏洞修复的版本5.0.6的版本。同时要注释其中一个插件(在5.0.6的版本下会存在一点问题),插件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
<junitArtifactName>junit:junit</junitArtifactName>
<argLine>-Xmx512m</argLine>
</configuration>
</plugin>

再一次访问http://127.0.0.1:8080/spring-mvc-showcase/resources/static/..%5c/..%5c/windows/win.ini

当程序运行至path = this.processPath(path);,可以看到path的值仍然是static/..\/..\/windows/win.ini

我们跟踪进入到processPath()方法中:

1
2
3
4
5
protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/"); // 替换反斜线为斜线,path此时为static/..//..//windows/win.ini
path = this.cleanDuplicateSlashes(path); // 去掉多余斜线,path此时为static/../../windows/win.ini
return this.cleanLeadingSlash(path);
}

经过processPath()最终得到path值为static/../../windows/win.ini。之后程序经过this.isInvalidPath(path)对path的非法性进行分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected boolean isInvalidPath(String path) {
if (!path.contains("WEB-INF") && !path.contains("META-INF")) {
if (path.contains(":/")) {
String relativePath = path.charAt(0) == '/' ? path.substring(1) : path;
if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
logger.trace("Path represents URL or has \"url:\" prefix.");
return true;
}
}

if (path.contains("..")) {
path = StringUtils.cleanPath(path);
if (path.contains("../")) {
logger.trace("Path contains \"../\" after call to StringUtils#cleanPath.");
return true;
}
}

return false;
} else {
logger.trace("Path contains \"WEB-INF\" or \"META-INF\".");
return true;
}
}

程序会进入到path.contains("..")分支中。经过path = StringUtils.cleanPath(path);得到../windows/win.ini,此时满足了path.contains("../")条件,被认为是非法路径。最终getResource()返回的就是null,程序无法读取到win.ini的内容返回404。

漏洞的修复其实就是阻止了跨目录的方式读取文件。漏洞修复的两个位置是:1. 修改了processPath()对路径的处理;2. 增加了isInvalidPath()对路径的合法性判断。

参考

  1. https://bl4ck.in/vulnerability/analysis/2018/04/10/Directory-Traversal-with-Spring-MVC-on-Window.html