作者:badcode@知道创宇404实验室
时间:2018年8月14日
漏洞简介
2018年4月5日,Pivotal公布了Spring MVC存在一个目录穿越漏洞(CVE-2018-1271)。该漏洞影响Spring Framework版本5.0到5.0.4、4.3到4.3.14以及不再受支持的旧版本。这些版本允许应用程序配置Spring MVC以提供静态资源(如CSS、JS、图像)。当Spring MVC的静态资源存放在Windows系统上时,攻击者可以通过构造特殊URL导致目录遍历漏洞。
漏洞影响
Spring Framework 5.0到5.0.4版本受影响。Spring Framework 4.3到4.3.14版本以及不再受支持的旧版本同样受影响。
漏洞利用条件
服务器运行在Windows系统上,并且使用file协议打开资源文件目录。
漏洞复现
复现环境: 操作系统:Windows Web代码:spring-mvc-showcase 中间件:Jetty
环境搭建步骤:
下载spring-mvc-showcase
git clone https://www.php.cn/link/1d3b870a10ac880f27c0b5b69756e749
修改pom.xml,使用Spring Framework 5.0.0。
2. 修改Spring MVC静态资源配置,参考官方文档
通过官方文档可知有两种配置方式,可自行选择。此处通过重写WebMvcConfigurer中的addResourceHandlers方法来添加新的资源文件路径。在org.springframework.samples.mvc.config.WebMvcConfig中添加以下代码,使用file://协议指定resources为静态文件目录。
registry.addResourceHandler("/resources/**").addResourceLocations("file:./src/main/resources/","/resources/");
使用Jetty启动项目
mvn jetty:run
至此,复现环境搭建完成。
复现过程及结果: 访问以下链接
https://www.php.cn/link/cd882239782e46c81f650fc5a6136d2e
可以看到成功读取到win.ini的内容。
漏洞分析
当外部访问静态资源时,会调用org.springframework.web.servlet.resource.ResourceHttpRequestHandler:handleRequest来处理,在此处设置断点进行调试。
跟进org.springframework.web.servlet.resource.ResourceHttpRequestHandler:getResource()。
在request中保存的路径是/spring-mvc-showcase/resources/%255c%255c..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/windows/win.ini。在request.getAttribute()函数取值时会进行URL解码操作,此时path的值为%5c%5c..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini。接下来会对path进行两次校验,将path和path解码后的值分别使用isInvalidPath函数检查。查看这个函数:
当path包含..时,会调用cleanPath函数对path处理。跟进:
这个函数的作用是将包含..的相对路径转换为绝对路径。例如/foo/bar/../经过cleanPath处理后会变成/foo/。
cleanPath的问题在于String[] pathArray = delimitedListToStringArray(pathToUse, "/");这允许空元素存在,也就是说cleanPath会把//当成一个目录,而操作系统不会把//当成一个目录。借用Orange大佬的一张图来说明。
继续回到流程,上面提到会对path进行两次校验,第一次调用isInvalidPath,path的值是%5c%5c..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini,因为path以/分割后没有元素等于..,所以path经过cleanPath处理后的值不变,继续之后的判断,path里面也不包含../,所以最终返回false,也就是通过了校验。
第二次调用isInvalidPath(URLDecoder.decode(path, "UTF-8")),此时参数值是\../../../../../../../../../windows/win.ini,经过cleanPath处理后的值是//windows/win.ini,之后继续判断,path里面也不包含../,最终返回false,也通过了校验。
通过两次校验后,继续向下执行。获取一个Resource对象:
path的值还是之前,getLocations()获取到的就是之前在配置文件中配置的路径file:./src/main/resources/,继续跟进:
跟进ResourceResolver类的resolveResource:
跟进PathResourceResolver的resolveResourceInternal:
进入到org.springframework.web.servlet.resource.PathResourceResolver的getResource():
此时的resourcePath就是之前的path,location就是之前getLocations()获取到的值。继续跟进this.getResource:
调用location.createRelative拼接得到文件的绝对路径,返回一个UrlResource对象:
返回到getResource函数:
此时,resource是一个UrlResource对象,可以看到值是file:src/main/resources/%5c%5c..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini,之后调用exists()方法检查该文件是否存在,调用isReadable()方法检测该文件是否可读。进入exists()方法:
这里会调用isFileURL对url进行判断,是否以file://协议来读取文件,这也是为什么配置静态目录时要使用file://协议。
通过判断后,会调用this.getFile()来获取这个文件对象,这个方法在org.springframework.util.ResourceUtils这个方法类里面,跟进:
这里对是否为file://协议又判断了一次,之后进行了一步最重要的操作new File(toURI(resourceUrl).getSchemeSpecificPart());,将resourceUrl转换为URL对象,最后调用URI类的getSchemeSpecificPart()获取到文件路径,而在getSchemeSpecificPart()里面是有一次decode操作的,也就是在这里把%5c解码成了,跟进:

最后返回到exists(),最终返回true,即文件存在。
之后调用isReadable()方法检测该文件是否可读时,同样会调用这个getFile,最终返回true,即文件可读。
至此,对于resource的判断都结束了。返回到org.springframework.web.servlet.resource.ResourceHttpRequestHandler:handleRequest(),获取到通过校验的resource后,就开始准备response的内容了,包含获取文件的类型(用于response的Content-type),文件的大小(用于response的Content-length)等等,最后调用this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);获取文件的内容并返回给用户。
跟进write():
跟进writeInternal,之后再跳到writeContent:
跟进resource.getInputStream():
可以看到,这里使用openConnection创建一个URLConnection实例,也是在openConnection方法内,会自动decode,把%5c解码成,然后返回文件的InputStream对象,最终读取内容返回给用户。
注意事项
这个漏洞可以在Tomcat下触发,因为payload的双URL编码。在Spring Framework大于5.0.1的版本(我的测试环境5.0.4),双URL编码payload是不行的,单次URL编码的payload却是可以的,这种情况下该漏洞就无法在Tomcat下触发了,因为在默认情况下Tomcat遇到包含%2f(/)、%5c()的URL直接返回http 400,而在Jetty下是可以触发的。至于为什么双URL编码不行,是因为org.springframework.web.servlet.resource.PathResourceResolver的getResource()多了一个encode操作。
如果是双URL编码payload的进来,在获取path的时候解码一次,经过一次isInvalidPath判断,然后进入到PathResourceResolver的getResource(),也就是上图的位置,这里又会重新编码一次,又回到了双编码的情况了。最后在文件判断是否存在exists()方法的时候,getSchemeSpecificPart()只能解码一次,之后是无法读取到文件的,也就是文件不存在。
所以这里要使用单次编码才行。
补丁分析
看官方的补丁,是在ResourceHttpRequestHandler的getResource()里面把processPath重写了:
在进入isInvalidPath之前调用processPath函数对path处理,替换反斜线为斜线,删掉多余斜线,从而导致在isInvalidPath里面校验不通过。如果使用双编码方式的话,会经过isInvalidEncodedPath,里面会先对path解码,然后调用processPath处理,最后经过isInvalidPath,同样无法通过检查。
漏洞修复
Spring Framework 5.(5.0到5.0.4)版本,建议更新到5.0.5版本。 Spring Framework 4.3.(4.3到4.3.14)版本,建议更新到4.3.15版本。 不再受支持的旧版本,建议更新到4.3.15版本或5.0.5版本。
相关链接
[1] CVE-2018-1271: Directory Traversal with Spring MVC on Windows
https://www.php.cn/link/f2c9a31f724a08b5a42210bed4a04263
[2] Breaking Parser Logic! Take Your Path Normalization Off and Pop 0days Out
https://www.php.cn/link/96021734e7ee66275cb9995123834c72
[3] Directory Traversal with Spring MVC on Windows
https://www.php.cn/link/3c2140d1a64d146fbd15082b3a34130c

以上就是Spring MVC 目录穿越漏洞(CVE-2018-1271)分析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号