Node.js Path Traversal
Guide: Examples
and Prevention

stackhawk

StackHawk|October 2, 2021

Let's look at what path traversal attacks are and what approaches we can take to mitigate them with Node.js.

Building secure, robust applications is a craft that requires a lot of consideration and effort. Making sure to cover the extensive list of potential vulnerabilities can be an enormous task that demands experience and guidance. One such vulnerability is the directory access security of our system, which is commonly exploited by path traversal attacks. 

Understanding that, however, should not deter you from approaching the problem head-on. After all, extensive and very comprehensive resources that can guide you exist all over the net. This article is intended to be one such resource. 

The purpose of this article is to serve as a guide to understanding path traversal attacks and what approaches we can take to mitigate them with Node.js. First, we will briefly explain what path traversal attacks are. Then we will explore some common examples. And finally, we will implement fixes for these exploits. By the end of this article, you should have a basic understanding of path traversal and be capable of implementing mitigation mechanisms in your platform.

Node.js Path Traversal Guide: Examples and Prevention image

Note that we intend for this article to be for Node.js developers specifically. Therefore, we expect that you have a basic understanding of the Node.js development stack. So, if you haven't dipped your toes in it yet, please check out the Node.js guides for more information. 

Talking Path Traversal

What is a path traversal attack? Well, it's an attack that takes advantage of poor access control implementations on the server side, specifically for file access. In these attacks, a bad actor attempts to access restricted files in the server by injecting invalid or malicious user input into the application. Think of it as SQL injection but on directories instead of the database. 

Now, it's pretty apparent why unauthorized access to server files is terrible. With this kind of power, an ill-intended individual can wreak havoc in our systems and compromise our user's information. 

Let's dive much deeper into the simple quirks that make this vulnerability possible and why it even matters what system your server is running in. Now, let's explore a few examples of path traversal attacks. 

Examples of Path Traversal Attacks

What does a typical path traversal attack look like, you might ask? 

Well, it's as simple as this: 

../../etc/passwd

Surprising, right? 

The core idea of path traversal is basically to find ways to get to folders that the developer and application did not intend for you to be able to get to. So, if you have a basic understanding of path logic and Linux or the command line, you can really go places on an unprotected application. 

NodeJS Path Traversal Guide: Examples and Prevention - Picture 2 image

Here are some examples of what these can look like. 

Relative Path Attack

A relative path attack is essentially what we illustrated above. By exploiting the user input validation, or lack thereof, attackers might attempt to access restricted files in the server. In this case, the passwd file contains our secrets on the server. 

A simple way to mitigate this vulnerability is, of course, to apply proper user input validation. By doing something as simple as using path.normalize() and sanitizing the user input—something you should always do, by the way—you can save yourself from a lot of headaches and issues down the road. 

Poison Null Bytes Attack

By adding a NULL byte, \0, at the end of a string 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? Something like this. 

/../../../../../../../../../../../../../../../../etc/passwd%00

Notice the %00 at the end, which our poor validation would end up translating as something like \0.txt\0 and potentially give access to the passwd file. Yikes! 

To prevent this kind of attack from thwarting security, you only need to validate the user input with the following:

if (user_input.indexOf('\0') !== -1) {
return respond('Access denied');
}

Relatively straightforward, right? 

Path traversal attacks are not particularly sophisticated. As we mentioned before, they depend on poor access control implementations or edge-case vulnerabilities from poorly updated code. However, they can be very dangerous, and we should mitigate them as much as possible. The good news is that it's not that hard to do so.

Other Mitigating Approaches to Path Traversal Attacks in Node.js

There are, of course, many more things we can do to cover more potential gaps in our security. JavaScript has matured enough to offer extensive documentation on different approaches to mitigation, but we will be listing a few here. 

Path Prefix Validation

What about allowing some level of traversal in your application? There might be cases where you want the application to enable the user to find files in different folders—for example, profile pictures and essays, both in their folders. You can implement hardcoded path validations like variables used when requesting specific resources, but by doing so, you could open yourself up to prefix path traversal attacks. 

An attacker can freely traverse the directories if the user can enter dots and slashes in the application without validating the resulting string.  To mitigate this, we need to validate that the user input does not contain these characters and strip them, or flat-out display an error. 

Allowlisting

Allowlisting is a straightforward and relatively effective method to reduce the potential for exploits. Of course, you won't always be able to use it, but you should when you can. 

A straightforward example of this consists of validating that the user input conforms to a certain predefined standard. For example, if you have coded your application only to create and work with files with lowercase alphanumeric characters, then you can validate that the user only inputs such characters.

if (!/^[a-z0-9]+$/.test(user_input)) {
return respond('Access denied');
}

By adding this validation to user inputs, you can have an extra layer of protection against malicious attacks. 

Path Concatenation

Finally, one way to address all these gaps and create a robust solution to all the potential vulnerabilities we might face is to implement a general validation scheme that comprises all these tests and makes a secure final path string. 

One example of a solution would look something like this:

var root = '/var/www/';
exports.validatePath = (user_input) => {
if (user_input.indexOf('\0') !== -1) {
return 'Access denied';
}
if (!/^[a-z0-9]+$/.test(user_input)) {
return 'Access denied';
}
var path = require('path');
var safe_input = path.normalize(user_input).replace(/^(\.\.(\/|\\|$))+/, '');
var path_string = path.join(root, safe_input);
if (path_string.indexOf(root) !== 0) {
return 'Access denied';
}
return path_string;
}

NodeJS Path Traversal Guide: Examples and Prevention - Picture 3 image

Lines of code in an integrated development environment.

As you can see, we have incorporated all the checks and validations already discussed to encompass any potential abuse of our system. 

Find and Fix Security Vulnerabilities

Final Thoughts

As JavaScript is such a mature and robust language, there are myriad ways to go around this. None of them is really the best. Just make sure that you find something that satisfies your needs and then adequately test your approach and implementation. 

As simple as it might seem, it is essential to make sure that we enforce good path traversal security policies. We must also work on covering as many potential gaps in our application as possible. Technology will, of course, continue to evolve, and more robust and comprehensive solutions will become available to mitigate these issues. However, don't forget that humans can always cover for the gaps in our systems, as long as we are thorough and creative with our approaches. 

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.


StackHawk  |  October 2, 2021