In creating robust and reliable web solutions for our clients, we developers must be informed of the web's myriad of exploits and vulnerabilities. Protecting our platforms from the extensive list of threats is undoubtedly a daunting task. That task demands years of experience and preparation.
However, knowing that should not prevent you from addressing the problem. After all, comprehensive resources that can guide you exist all over the net.
This article aims to address the specific threat that targets the directory access security of our system—path traversal vulnerabilities. In addition, this article will provide a short but comprehensive guide for understanding path traversal attacks. Moreover, we will explore how we can mitigate their impact with Java and Spring.
We will start by briefly explaining what path traversal attacks are.
Then we will examine some basic examples.
Lastly, we will provide some mitigating strategies for these exploits.
By the end of this article, you should have a basic comprehension of what path traversal attacks are and be able to implement mitigation strategies in your platform.
Note that we crafted this article for Spring developers in particular. Therefore, we expect that you have prior knowledge of the Spring development stack. However, if you haven't dipped your toes into it yet, please check this Spring Boot guide for more information.
Building a Simple Spring Boot Project
Before we get into the nitty-gritty, we think it is important to briefly go about building a simple Spring Boot web project for Java. All you will need is a JDK installed, either Gradle or Maven, and your favorite IDE.
To expedite the process, we will start by going to the Spring Initializr to get our start-up project. Remember to select "Spring Web" as a dependency before downloading the zip file.
Once you have done this, proceed to the src/main/java/com/example/demo folder and create a class file called "HelloController.java."
In it, just input the following code:
package com.example.springboot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}
There you go, that's all you need. You can proceed to run the code and go to localhost:8080, and your page will load.
Explaining Path Traversal
Path traversal in itself is a simple concept to grasp. However, it is crucial to properly understand the underlying mechanisms that enable this kind of exploit to work so we can mitigate its impact and damage.
Path traversal is an attack that exploits weak access control implementations on the server side, particularly for file access. In these attacks, an attacker would try to access restricted files by injecting invalid or malicious input into the website. You can think of it as SQL injection but on directories instead of databases.
If the attacker succeeds at their endeavor, that would be pretty terrible for our platform, as it would basically compromise the whole server. Goodbye, security and service.
In this article, we go into more detail about the peculiarities that make this vulnerability possible and why it even matters what system your server is running in.
Let's proceed to examine some path traversal attack examples.
Path Traversal Attack Examples
Now that we have a basic understanding of path traversal, what does an attack actually look like?
Well, something as simple as this:
curl http://mysecuresite.com/public/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/root/.ssh/id_rsa
Shockingly simple, right?
As you can see, the main goal is to get access to unauthorized folders in your server. With a basic understanding of path logic, Linux, or the command line, an attacker can go places on an application that is not protected.
Now, let's see some examples.
Relative Path Attack
A relative path attack works by exploiting the existing user input validation, or lack thereof. Much like how we illustrated above, attackers might try to access restricted directories in the server by inputting special characters or encoded strings.
In this case, the id_rsa file contains valuable information on the server. To mitigate this vulnerability, we simply have to make sure to implement proper user input validation in every avenue.
Sanitizing the user input and using path.normalize()—something you should always do, by the way—can go a long way in preventing a catastrophe and saving yourself from a lot of headaches and problems down the road.
Poison Null Bytes Attack
If you don't know what a null byte is, don't worry. It's not as complicated as it sounds. A null byte represented as \0 is an encoded character that is usually not visible and is used only for encoding purposes.
By appending the \0 at the end of an URL in an HTTP request, the attacker can bypass the string validation used to sanitize user input and get access to unauthorized files and directories.
What would that look like in code? Well, something like this:
curl http://mysecuresite.com/public/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/root/passwd%00
Notice here that the %00 that lies at the end would potentially cause our poor validation to end up translating to something like \0.txt\0 and giving access to the passwd file. Yikes!
Yet preventing this kind of attack from besting our security is simple enough. We need only to validate the user input.
We can achieve that with the following code:
private static final String BASE_PATH ="/etc/user_content";
public static String getContent(String path) throws IOException {
String normalized_path = Paths.get(path).normalize().toString();
File file = new File(BASE_PATH, normalized_path);
if (file.getCanonicalPath().startsWith(BASE_PATH)) {
String content = new String(Files.readAllBytes(file.toPath()));
return content;
} else {
return "Access Error";
}
}
Well, that seems simple, right?
The truth is that path traversal attacks are not incredibly sophisticated in their implementation. Instead, as mentioned before, they depend on inadequate access control mitigations or library vulnerabilities introduced by inadequately maintained code.
However, it's important to remember that they can still be very dangerous, and we should mitigate them as much as possible. Naturally, we don't want our users to be mingling on protected directories and accessing sensitive files. Therefore, it is vital to implement these checks whenever possible.
The good news is that it is not difficult to do so.
Mitigating Path Traversal Vulnerabilities
Commonly, our development kits, packages, and libraries are pretty robust and have already implemented many layers of protection against path traversal. However, there is always the possibility that we introduce a vulnerability in our code unintentionally.
What if you need to allow some degree of traversal in your application?
There could be situations where your client requires the application to allow the user to locate files in different folders, such as profile pictures and essays. You could, of course, implement hardcoded path validations used when requesting particular resources. But, by doing so, you could potentially open yourself to prefix path traversal attacks.
Path Prefix Validation
In this particular scenario, an attacker could easily traverse the directories in the server by inputting dots and slashes in the application.
In order to mitigate the attack mentioned above, we must validate the user input and ensure that it does not contain invalid characters. We can then either strip them from the string or return an error.
Safelisting
Safelisting consists of creating a list of possible paths that can be accessed safely and comparing all imputed strings with their values.
For example, if you designed your application to create and use files named with lowercase alphanumeric characters, you can validate that the user input conforms to this standard.
private static final String BASE_PATH ="/etc/user_content";
private static final Set<String> VALID_PATHS = new HashSet<String>(Arrays.asList("/profile", "/photos", "/music"));
public static String getContent(String path) throws IOException {
String normalized_path = Paths.get(path).normalize().toString();
if (VALID_PATHS.contains(normalized_path)) {
File file = new File(BASE_PATH, normalized_path);
if (file.getCanonicalFile().toPath().startsWith(Paths.get(BASE_PATH)) {
String content = new String(Files.readAllBytes(file.toPath()));
return content;
} else {
return "Access Error";
}
} else {
return "Access Error";
}
}
Safelisting is a simple and comparatively effective approach to reducing the potential for exploits. By adding this validation to user inputs, you can have an extra layer of protection against malicious attacks.
Validations in an integrated development environment
As you can see in this example, we can achieve a decent amount of protection with some simple validations. However, keep in mind that the best defense is still to restrict user input and only allow specific controlled access when absolutely necessary.
Finally, there are countless approaches we can take to close the possible gaps in our security. However, if you want to find specific solutions for your particular use case, you can undoubtedly depend on the solid and extensive Java documentation base and community.
Last Thoughts
Being that Java is now a pretty mature and battle-tested language, there are endless ways to solve this particular vulnerability. Therefore, it's crucial to find and choose an approachable and effective solution that satisfies your needs and doesn't introduce excessive complexity. And, as always, make sure to test your approach and implementation adequately.
This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.