Django Path Traversal
Guide: Examples
and Prevention

stackhawk

StackHawk|September 28, 2021

In this post, we’ll discuss Django path traversal including examples and ways to prevent each vulnerability.

Django path traversal or directory traversal is a web security vulnerability that gives a remote attacker access to files and directories that are stored outside the specified folder to which the application grants access. The attacker can achieve this by manipulating the files with a “dot-dot-slash” (../) sequence. Another name for path traversal is the “dot-dot-slash attack.” 

Path traversal is also possible at the Django SSI template tag. It’s possible to manipulate the SSI template tag to gain access to arbitrary files stored in the system. In addition, this manipulation makes it possible for an attacker to know the folder structure of your application. This allows the attacker to copy, read, and modify files stored in these paths. Now, these files might include your application source code, credentials for back-end systems, sensitive information, and data. With this vulnerability, the attacker can sometimes take full control of the server. 

In this post, we’ll look at some common examples of path traversal and ways to prevent each vulnerability. 

Generally, path traversal can be divided into two categories: information disclosure and writing to arbitrary files. Directory traversal also is called directory climbing or backtracking. To understand path traversal better, let’s look at some of the causes of the vulnerability.

Find and Fix Security Vulnerabilities

Causes of Path Traversal Attack in Django

Some of the causes of path traversal in Django include the following: 

  • Lack of URL checking

  • Lack of relative path checking

  • Insufficient handling of a request path or URL

  • Neglect to check the file name of an attachment

Examples

In this section, we’ll be looking at some examples of path traversal and how to prevent each. 

1. Using the SSI Template Tag

An attacker can use the SSI template tag to have access to arbitrary files and directories in your server. Now, what is an SSI template tag? The SSI template tag in Django allows you to include files in a template by absolute path. For example, here’s how you can include a file in the template using the SSI template tag: 

{% ssi /home/html/myImage.jpg %}

ALLOW_INCLUDE_ROOTS

Django’s configuration includes an ALLOW_INCLUDE_ROOTS option that has a default value of an empty tuple (). ALLOW_INCLUDE_ROOTS contains a tuple of strings representing allowed prefixes for the SSI template tag. These prefixes are for security measures so that an attacker cannot have access to files on the server except for the ones whose prefixes are among the allowed prefixes in the ALLOW_INCLUDE_ROOTS setting. 

For instance, in your settings, if the value of ALLOW_INCLUDE_ROOTS is set to (/home/html, var/html), and in your template, you state: 

{% ssi /home/html/myImage.jpg %}

This will work because the prefix of your /home/html/myImage.jpg file is among the allowed prefixes that are in the ALLOWED_INCLUDE_ROOTS settings. 

However, say you try something like: 

{% ssi /etc/passwd/credentials.txt %}

Now, this is never going to work because the above prefix is not in the ALLOWED_INCLUDE_ROOTS settings. And that’s where a path traversal attack (or directory attack or dot-dot-slash attack) might come in handy to the attacker. 

Using the dot-dot-slash sequence, an attacker might be able to get into the /etc/passwd directory even though it’s not among the allowed prefixes. And why is that? For example, if the /etc directory is in the same folder as the /home directory, an attacker can use the dot-dot-slash to get into the /etc folder, using the following code: 

{% ssi /home/html/../../etc/passwd %}

Since the /home/html prefix is among the allowed prefixes that we stated in the ALLOWED_INCLUDE_ROOTS setting, putting the /home/html/../../etc/passwd to the SSI template will get the attacker into the /etc/passwd directory. 

In case you’re wondering how, adding ../ means going one level out of the current directory. In our case, our current directory is /html; now adding double ../../ dot-dot-slash means going two levels out of the current directory. As a result, this takes us into that directory where /home and /etc are stored. And from there, we can get into the /etc/passwd/ directory and access its files by just writing the file path after the dot-dot-slash sequence.

Django Path Traversal Guide: Examples and Prevention image

Prevention

So, how can you prevent this kind of attack? 

The Django team was notified of the vulnerability in the SSI template tag and they made an amendment. The SSI template that wasn’t using Python’s os.path.abspath method to determine the absolute path of the file and whether it’s located in the permitted directory by ALLOWED_INCLUDE_ROOTS is now reinforced to use the os.path.abspath

So all you have to do is upgrade Django to a newer version. You can do that by running the following commands, depending on your machine. 

On Windows: 

py -m pip install -U Django

On Linux and Mac: 

python -m pip install -U Django

Django versions that are vulnerable to path traversal attack via the SSI template tag include version 1.4.x before 1.4.7, version 1.5.x before 1.5.3, and version 1.6.x before 1.6 beta 3. So if you're using any of these versions, all you have to do is upgrade.

2. Via HTTP Request

Another example of path traversal in Django is by manipulating file names and making requests. For example, you have an <img/> element in your template that looks like this: 

<img src=”/getImage?filename=sushi.jpg” />

The getImage URL takes a filename parameter that returns the specified file requested. Now let’s say your images are stored in a folder somewhere, like /restaurant/menu/images. The application appends the requested file name with the directory, /restaurant/menu/images/, which will turn to /restaurant/menu/images/sushi.jpg. Then the file system API is used to read the file. 

However, this has no defense against path traversal attacks. So, an attacker might use your site’s full URL to gain access to restricted content. Let's look at the following example: 

https://my-django-site.me/getImage?filename=../../../etc/passwd

Here, as we already know, this is valid within the file path, because initially the base directory (/restaurant/menu/images/) is appended to your request, which will become /restaurant/menu/images/../../../etc/passwd. And now this can possibly take the user to the root and get access into the /etc/passwd/ folder. 

Unless you’re using an old version of Django, Django checks for path traversal in your requests by default. But there are various ways in which an attacker might bypass Django path traversal checks. For example, an attacker might be able to use various encoding and double encoding, such as: 

  • %2e%2e/, which represents ../ (dot-dot-slash)

  • %2e%2e%2f,  which represents ../ (dot-dot-slash)

  • ..%2f, which represents ../ (dot-dot-slash)

  • %2e%2e\, which represents ..\ (both ../ and ..\ are valid traversal sequences on Windows)

And so on. Then an attack like the following is likely going to escape through: 

https://my-django-site.me/getImage?filename=%2e%2e%2fetc/passwd

So, how can you avoid this kind of attack?

Prevention Tip #1

One of the easiest things you can do is to update to the latest version of Django, but that won’t solve it all. In your code, you have to validate the URL requests and also the file names of attachments. For example, /restaurant/menu/images/sushi.jpg isn’t the same as /restaurant/menu/images/../../etc/passwd

We can use the Python os.path.realpath to get the canonical path of the specified file name or directory by removing any symlink (or symbolic links). The dot-dot-slash is an example of a symbolic link. 

Now we can validate the requested file or path like this:

attackersUrl = ‘/restaurant/menu/images/../../etc/passwd’
attackersUrl = os.path.realpath(attackersUrl)

This will return /etc/passwd/. Then we can compare whether the attacker’s requested path has a common prefix with our base directory, which is /restaurant/menu/images/, by using the os.path.commonprefix method.

The following code compares the requested path:

attackersUrl = ‘/restaurant/menu/images/../../etc/passwd’
attackersUrl = os.path.realpath(attackersUrl)
baseDir = ‘/restaurant/menu/images/’
commonPrefix = os.path.commonprefix([attackersUrl, baseDir])
if commonPrefix != baseDir:
#trying to attack using dot-dot-slash do not return file or path requested

Prevention Tip #2

We can also use the Python os.path.relpath to convert the requested path into a path relative to our base directory, which is /restaurant/menu/images/. After that, we can convert the requested path again into an absolute path using the os.path.abspath method, which will return the absolute path URL of the attacker’s requested path. 

Here’s some code to demonstrate this solution:

baseDir = “/restaurant/menu/images/”
attackersUrl = “/restaurant/menu/images/../../etc/passwd”
os.path.relpath(attackersUrl, start=baseDir)

The above code will return ..\\..\\etc\\passwd, depending on the operating system you’re using. After that, we can use the python os.path.abspath to convert the requested path (attackersUrl) into an absolute path from the main directory. We’ll then use the os.path.commonprefix to check for a common prefix between our base directory (/restaurant/menu/images/) with the recently converted absolute path of the attackers URL.

attackersUrl = os.path.abspath(attackersUrl)

From the code snippet above, attackersUrl will return an absolute path, which will become (depending on your application directory structure): 

https://my-django-site.me/var/www/etc/passwd

From there, we can compare the common prefix using the following code: 

os.path.commonprefix([attackersUrl, baseDir])

Now this will return an empty string because there’s no common prefix between the two paths. So from there you can choose not to return the requested file or path.

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

Conclusion

A general solution to Django path or directory traversal attack is to upgrade to the latest version. This is because the maintainers of Django identify and fix security vulnerabilities in new versions. 

However, be sure to validate user inputs before you use them in your application. Doing so gives an extra layer of security and makes it harder for attackers to hack your site. 

This post was written by Pius Aboyi. Pius is a mobile and web developer with over 4 years of experience building for the Android platform. He writes code in Java, Kotlin, and PHP. He loves writing about tech and creating how-to tutorials for developers.


StackHawk  |  September 28, 2021