Author:Badcode@Knownsec 404 Team
Date: August 14, 2018
Chinese Version: https://paper.seebug.org/665/
On April 5, 2018, Pivotal released a Directory Traversal vulnerability in Spring MVC (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.
Windows Operating system
web code spring-mvc-showcase
Middleware jetty
1.Download spring-mvc-showcase
git clone https://github.com/spring-projects/spring-mvc-showcase.git
Modify pom.xml
to use Spring Framework 5.0.0.
2.Modify Spring MVC static resource configuration. You can refer to official documentation
According to the official documentation, there are two ways to configure it. Add a new resource file path here by overriding the addResourceHandlers
method in WebMvcConfigurer
. Add the following code to org.springframework.samples.mvc.config.WebMvcConfig
. It uses the file://
protocol to specify resources
as the static file directory.
registry.addResourceHandler("/resources/**").addResourceLocations("file:./src/main/resources/","/resources/");
3.Start the Project with Jetty
Now the environment has been completed.
Visit the link below:
http://localhost:8080/spring-mvc-showcase/resources/%255c%255c..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/windows/win.ini
You can see the contents of the win.ini
successfully read.
When the external access to the static resource, it will call org.springframework.web.servlet.resource.ResourceHttpRequestHandler:handleRequest
to handle the breakpoint debugging here.
Follow up with org.springframework.web.servlet.resource.ResourceHttpRequestHandler:getResource()
.
The path saved in request
is /spring-mvc-showcase/resources/%255c%255c..%255c/..%255c/..%255c/..%255c/..%255c/.. %255c/..%255c/..%255c/..%255c/windows/win.ini
. The url decode operation is performed when the request.getAttribute()
function runs. The path
is %5c%5c..%5c/..%5c/..%5c/..%5c /..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini
. Next, the path will be checked twice, and the decoded values of path
and path
will be checked by the isInvalidPath
function. Let's look at this function.
When path
contains ..
, the cleanPath
function is called to handle path
. Follow up with:
The purpose of this function is to convert this relative path containing ..
into an absolute path. For example, /foo/bar/../
will become /foo/
after being processed by cleanPath
.
The problem with cleanPath
is String[] pathArray = delimitedListToStringArray(pathToUse, "/");
. This allows empty elements to exist, that is, cleanPath
will treat //
as a directory, while the operating system does not. Quote an image from @Orange.
The above said that the path will be checked twice. When first calling isInvalidPath
, the path
is %5c%5c..%5c/..%5c/..% 5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini
. Since there is no element equals to ..
after path
is separated by /
, the path
will not change after being processed by cleanPath
. In the next judgment, the path
does not contain ../
, so it finally returns false
, which means it passes the check.
When calling isInvalidPath(URLDecoder.decode(path, "UTF-8"))
for the second time, the parameter is \\..\/..\/..\/..\/..\/ ..\/..\/..\/..\/windows/win.ini
. Its value after being processed by cleanPath
is //windows/win.ini
. In the next judgment, the path
does not contain ../
. It finally returns false and also passes the check.
Continue and get a Resource
object.
The path
has not changed. getLocations()
gets the path file:./src/main/resources/
that was previously configured in the configuration file. Follow up.
Follow the resolveResource
of the ResourceResolver
class.
Follow the resolveResourceInternal
of PathResourceResolver
.
Go to getResource()
of org.springframework.web.servlet.resource.PathResourceResolver
.
At this time, resourcePath
is the previous path
, and location
is the value obtained by getLocations()
. Continue to follow up with this.getResource
.
Call location.createRelativ
to get the absolute path of the file and return a UrlResource
object.
Return to the getResource
function.
Here resource
is a UrlResource
object, and you can see its value is file:src/main/resources/%5c%5c..%5c/..%5c/..%5c/..%5c /..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini
. Then call exists()
method to check if the file exists, and call isReadable ()
method to detect whether the file is readable. Go into the exists()
method.
Here we will call isFileURL
to determine whether the url
reads the file with the file://
protocol, which is why the file://
protocol is used when configuring the static directory.
After the judgment, this.getFile()
will be called to get this file object. This method is in the method class of org.springframework.util.ResourceUtils
. Follow up with:
Here is the judgment of whether it is the file://
protocol, and then new File(toURI(resourceUrl).getSchemeSpecificPart());
to convert resourceUrl
to a URL object. Finally call getSchemeSpecificPart()
of the URI
class to get the file path. There is a decode
operation in getSchemeSpecificPart()
, which is to decode %5c
into \
. Follow up with:
It finally returns to exists()
and returns true
, which means file exists.
Later when calling the isReadable()
method to check if the file is readable, the getFile
will also be called. It will return true
, which means the file is readable.
At this point, the judgment for resource
is over and we will go back to org.springframework.web.servlet.resource.ResourceHttpRequestHandler:handleRequest()
. After getting resource
that has passed the check, we should start preparing the response content, including getting the type of the file (for the content of response) -type), the size of the file (for Content-length of response), etc.. Finally call this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
to get the contents of the file and return it to the user.
Follow up with write()
.
Follow up with writeInternal
and jump to writeContent
.
Follow up with resource.getInputSream()
As you can see, here you create a URLConnection
instance with openConnection
. In the openConnection
method, it decodes %5c
into \
and returns the InputStream
object of the file decode
. And it finnaly reads the content and returns it to the user.
This vulnerability can be triggered under Tomcat because of the double URL encoding of the payload.
In the Spring Framework version greater than 5.0.1 (my test environment is 5.0.4), double URL encoding payload will not work, but a single URL encoded payload works. In this case the vulnerability can not be triggered under Tomcat, because by default when Tomcat encounters a URL containing %2f(/)
and %5c(\)
, it directly returns http400, which can be triggered under jetty.
As for why double URL encoding is not working, it is because getResource()
of org.springframework.web.servlet.resource.PathResourceResolver
has an extra encode
operation.
If it is a double URL encoding payload, decode it once when getting path
. After the isInvalidPath
judgment, then enter the getResource()
of PathResourceResolver
as the picture shows. There it will be coded again and returns to double encoding. Finally, when the file determines whether the exists()
method exists, getSchemeSpecificPart()
can only be decoded once, and then the file cannot be read, which means the file does not exist.
So here you have to use a single encoding.
Look at the official patches which is rewritten up by processPath
in getResource()
of ResourceHttpRequestHandler
.
Call function processPath
to process path
before entering isInvalidPath
. Replace the backslash with a slash, and remove the extra slash, prevent it from passing the check in isInvalidPath
. If you use the double encoding method, it will pass isInvalidEncodedPath
, which will first decode path
, then call processPath
, and finally pass isInvalidPath
, which will not pass the check either.
Beijing Knownsec Information Technology Co., Ltd. was established by a group of high-profile international security experts. It has over a hundred frontier security talents nationwide as the core security research team to provide long-term internationally advanced network security solutions for the government and enterprises.
Knownsec's specialties include network attack and defense integrated technologies and product R&D under new situations. It provides visualization solutions that meet the world-class security technology standards and enhances the security monitoring, alarm and defense abilities of customer networks with its industry-leading capabilities in cloud computing and big data processing. The company's technical strength is strongly recognized by the State Ministry of Public Security, the Central Government Procurement Center, the Ministry of Industry and Information Technology (MIIT), China National Vulnerability Database of Information Security (CNNVD), the Central Bank, the Hong Kong Jockey Club, Microsoft, Zhejiang Satellite TV and other well-known clients.
404 Team, the core security team of Knownsec, is dedicated to the research of security vulnerability and offensive and defensive technology in the fields of Web, IoT, industrial control, blockchain, etc. 404 team has submitted vulnerability research to many well-known vendors such as Microsoft, Apple, Adobe, Tencent, Alibaba, Baidu, etc. And has received a high reputation in the industry.
The most well-known sharing of Knownsec 404 Team includes: KCon Hacking Conference, Seebug Vulnerability Database and ZoomEye Cyberspace Search Engine.
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/991/