If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:
>> LEARN SPRING SECURITYMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Introduction
Spring Framework versions 5.0 to 5.0.4, 4.3 to 4.3.14, and other older versions had a directory or path traversal security vulnerability on Windows systems.
Misconfiguring the static resources allows malicious users to access the serverβs file system. For instance, serving static resources using file: protocol provides illegal access to the file system on Windows.
The Spring Framework acknowledged the vulnerability and fixed it in the later releases.
Consequently, this fix guards the applications against path traversal attacks. However, with this fix, a few of the earlier URLs now throw an org.springframework.security.web.firewall.RequestRejectedException exception.
Finally, in this tutorial, letβs learn about org.springframework.security.web.firewall.RequestRejectedException and StrictHttpFirewall in the context of path traversal attacks.
2. Path Traversal Vulnerabilities
A path traversal or directory traversal vulnerability enables illegal access outside the web document root directory. For instance, manipulating the URL can provide unauthorized access to the files outside the document root.
Though most latest and popular webservers offset most of these attacks, the attackers can still use URL-encoding of special characters like β./β, β../β to circumvent the webserver security and gain illegal access.
Also, OWASP discusses the Path Traversal vulnerabilities and the ways to address them.
3. Spring Framework Vulnerability
Now, Letβs try to replicate this vulnerability before we learn how to fix it.
First, letβs clone the Spring Framework MVC examples. Later, letβs modify the pom.xml and replace the existing Spring Framework version with a vulnerable version.
Clone the repository:
git clone [email protected]:spring-projects/spring-mvc-showcase.git
Inside the cloned directory, edit the pom.xml to include 5.0.0.RELEASE as the Spring Framework version:
<org.springframework-version>5.0.0.RELEASE</org.springframework-version>
Next, edit the web configuration class WebMvcConfig and modify the addResourceHandlers method to map resources to a local file directory using file:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/resources/**")
.addResourceLocations("file:./src/", "/resources/");
}
Later, build the artifact and run our web app:
mvn jetty:run
Now, when the server starts up, invoke the URL:
curl 'http://localhost:8080/spring-mvc-showcase/resources/%255c%255c%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/windows/system.ini'
%252e%252e%255c is a double-encoded form of ..\ and %255c%255c is a double-encoded form of \\.
Precariously, the response will be the contents of the Windows system file system.ini.
4. Spring Security HttpFirewall Interface
The Servlet specification does not precisely define the distinction between servletPath and pathInfo. Hence, there is an inconsistency among the Servlet containers in the translation of these values.
For instance, on Tomcat 9, for the URL http://localhost:8080/api/v1/users/1, the URI /1 is intended to be a path variable.
On the other hand, the following returns /api/v1/users/1:
request.getServletPath()
However, the command below returns a null:
request.getPathInfo()
Unable to distinguish the path variables from the URI can lead to potential attacks like Path Traversal / Directory Traversal attacks. For instance, a user can exploit system files on the server by including a \\, /../, ..\ in the URL. Unfortunately, only some Servlet containers normalize these URLs.
Spring Security to the rescue. Spring Security consistently behaves across the containers and normalizes these kinds of malicious URLs utilizing a HttpFirewall interface. This interface has two implementations:
4.1. DefaultHttpFirewall
In the first place, letβs not get confused with the name of the implementation class. In other words, this is not the default HttpFirewall implementation.
The firewall tries to sanitize or normalize the URLs and standardizes the servletPath and pathInfo across the containers. Also, we can override the default HttpFirewall behavior by explicitly declaring a @Bean:
@Bean
public HttpFirewall getHttpFirewall() {
return new DefaultHttpFirewall();
}
However, StrictHttpFirewall provides a robust and secured implementation and is the recommended implementation.
4.2. StrictHttpFirewall
StrictHttpFirewall is the default and stricter implementation of HttpFirewall. In contrast, unlike DefaultHttpFirewall, StrictHttpFirewall rejects any un-normalized URLs providing more stringent protection. In addition, this implementation protects the application from several other attacks like Cross-Site Tracing (XST) and HTTP Verb Tampering.
Moreover, this implementation is customizable and has sensible defaults. In other words, we can disable (not recommended) a few of the features like allowing semicolons as part of the URI:
@Bean
public HttpFirewall getHttpFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall.setAllowSemicolon(true);
return strictHttpFirewall;
}
In short, StrictHttpFirewall rejects suspicious requests with a org.springframework.security.web.firewall.RequestRejectedException.
Finally, letβs develop a User Management application with CRUD operations on Users using Spring REST and Spring Security, and see StrictHttpFirewall in action.
5. Dependencies
Letβs declare Spring Security and Spring Web dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.5</version>
</dependency>
6. Spring Security Configuration
Next, letβs secure our application with Basic Authentication by creating a configuration class that creates a SecurityFilterChain bean:
@Configuration
public class HttpFirewallConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry.requestMatchers("/error").permitAll().anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
By default, Spring Security provides a default password that changes for every restart. Hence, letβs create a default username and password in the application.properties:
spring.security.user.name=user
spring.security.user.password=password
Henceforth, weβll access our secured REST APIs using these credentials.
7. Building a Secured REST API
Now, letβs build our User Management REST API:
@PostMapping
public ResponseEntity<Response> createUser(@RequestBody User user) {
userService.saveUser(user);
Response response = new Response()
.withTimestamp(System.currentTimeMillis())
.withCode(HttpStatus.CREATED.value())
.withMessage("User created successfully");
URI location = URI.create("/users/" + user.getId());
return ResponseEntity.created(location).body(response);
}
@DeleteMapping("/{userId}")
public ResponseEntity<Response> deleteUser(@PathVariable("userId") String userId) {
userService.deleteUser(userId);
return ResponseEntity.ok(new Response(200,
"The user has been deleted successfully", System.currentTimeMillis()));
}
Now, letβs build and run the application:
mvn spring-boot:run
8. Testing the APIs
Now, letβs start by creating a User using cURL:
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api/v1/users
Here is a request.json:
{
"id":"1",
"username":"navuluri",
"email":"[email protected]"
}
Consequently, the response is:
HTTP/1.1 201
Location: /users/1
Content-Type: application/json
{
"code":201,
"message":"User created successfully",
"timestamp":1632808055618
}
Now, letβs configure our StrictHttpFirewall to deny requests from all the HTTP methods:
@Bean
public HttpFirewall configureFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall
.setAllowedHttpMethods(Collections.emptyList());
return strictHttpFirewall;
}
Next, letβs invoke the API again. Since we configured StrictHttpFirewall to restrict all the HTTP methods, this time, we get an error.
In the logs, we have this exception:
org.springframework.security.web.firewall.RequestRejectedException:
The request was rejected because the HTTP method "POST" was not included
within the list of allowed HTTP methods []
Since Spring Security v6.1.5, we can use RequestRejectedHandler to customize the HTTP Status when there is a RequestRejectedException:
@Bean
public RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}
Note that the default HTTP status code when using a HttpStatusRequestRejectedHandler is 400. However, we can customize this by passing a status code in the constructor of the HttpStatusRequestRejectedHandler class.
Now, letβs reconfigure the StrictHttpFirewall to allow \\ in the URL and HTTP GET, POST, DELETE, and OPTIONS methods:
strictHttpFirewall.setAllowBackSlash(true);
strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("GET","POST","DELETE", "OPTIONS")
Next, invoke the API:
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api\\v1/users
And here we have a response:
{
"code":201,
"message":"User created successfully",
"timestamp":1632812660569
}
Finally, letβs revert to the original strict functionality of StrictHttpFirewall by deleting the @Bean declaration.
Next, letβs try to invoke our API with suspicious URLs:
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api/v1//users
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api/v1\\users
Straightaway, all the above requests fail with error log:
org.springframework.security.web.firewall.RequestRejectedException:
The request was rejected because the URL contained a potentially malicious String "//"
9. Conclusion
This article explains Spring Securityβs protection against malicious URLs that may cause the Path Traversal/Directory Traversal attacks.
DefaultHttpFirewall tries to normalize the malicious URLs. However, StrictHttpFirewall rejects the requests with a RequestRejectedException. Along with Path Traversal attacks, StrictHttpFirewall protects us from several other attacks. Hence it is highly recommended to use the StrictHttpFirewall along with its default configurations.
