Understanding Path Traversal Vulnerability in Python Flask: A Practical Security Guide

In web application security, one small mistake in handling user input can expose sensitive files on an entire server.

PythonCyberSecurityWebSecurityPathTraversalApplicationSecurity

Building something?

Join the TermTrix builders community

Join

One such vulnerability is Path Traversal, also known as Directory Traversal.

It occurs when an application allows users to access files through a user-controlled path without properly validating where that path points.

In this article, we'll understand how Path Traversal works, explore a vulnerable Flask application, reproduce the vulnerability in a local environment, and learn how to fix it.

1. What Is Path Traversal?

Path Traversal is a security vulnerability that allows an attacker to navigate outside an application's intended directory and potentially access sensitive files on the server.

Consider an application that allows users to download files.

The application stores downloadable files in:

/var/www/app/files/

Ideally, users should only be able to access files inside this directory.

However, an attacker might manipulate the filename using ../ sequences to navigate to parent directories.

For example:

../../etc/passwd

The ../ sequence tells the operating system to move one directory level upward.

By combining multiple sequences, an attacker may escape the intended directory.

2. Let's Understand the Vulnerable Code

Consider the following Flask application:

from flask import Flask, request, Response
import os

app = Flask(__name__)

FILES = "/var/www/app/files"

@app.route("/download")
def download():
    f = request.args.get("file", "readme.txt")

    path = os.path.join(FILES, f)

    try:
        return Response(
            open(path, "rb").read(),
            mimetype="text/plain"
        )
    except Exception as e:
        return Response("error: %s" % e, status=404)

At first glance, this implementation looks simple.

It accepts a filename, joins it with the application's file directory, and returns the requested file.

But there is a security issue.

Look at this line:

f = request.args.get("file", "readme.txt")

The application directly accepts user-controlled input.

Next:

path = os.path.join(FILES, f)

It joins the user-provided filename with the intended directory without checking whether the resulting path stays inside that directory.

This is the root cause of the vulnerability.

3. How Does an Attacker Exploit It?

Assume the application is running at:

http://localhost:5000

A normal request looks like this:

GET /download?file=readme.txt

The application reads:

/var/www/app/files/readme.txt

Now imagine an attacker sends:

GET /download?file=../../etc/passwd

The application constructs:

/var/www/app/files/../../etc/passwd

After resolving the parent directory references, the resulting path becomes:

/etc/passwd

If the application process has permission to read this file, its contents may be exposed.

Other files that could be exposed

Depending on the operating system, application configuration, and filesystem permissions, attackers might attempt to access:

  • /etc/passwd – Linux user account information.
  • /etc/hostname – System hostname.
  • Application configuration files.
  • Environment files containing secrets.
  • Database configuration files.
  • Private keys and other sensitive files.

The impact depends on the application's permissions and the files accessible to its process.

4. Understanding the Root Cause

The main issue is not simply the presence of ../.

The actual problem is trusting user input when constructing filesystem paths.

For example:

path = os.path.join(FILES, f)

Python's os.path.join() does not guarantee that the resulting path remains inside FILES.

It is a path-combination function, not a security validation function.

An application must explicitly verify the final resolved path.

5. How to Fix Path Traversal

One approach is to use os.path.realpath() to resolve the absolute path and verify that it stays within the permitted directory.

Here is a safer implementation:

from flask import Flask, request, Response
import os

app = Flask(__name__)

FILES = "/var/www/app/files"

@app.route("/download")
def download():
    f = request.args.get("file", "readme.txt")

    base_dir = os.path.realpath(FILES)
    requested_path = os.path.realpath(
        os.path.join(base_dir, f)
    )

    if os.path.commonpath(
        [base_dir, requested_path]
    ) != base_dir:
        return Response("Forbidden", status=403)

    try:
        with open(requested_path, "rb") as file:
            return Response(
                file.read(),
                mimetype="text/plain"
            )

    except OSError:
        return Response("Not found", status=404)

What changed?

1. Resolve the base directory

base_dir = os.path.realpath(FILES)

This resolves the actual absolute path of the permitted directory.

2. Resolve the requested path

requested_path = os.path.realpath(
    os.path.join(base_dir, f)
)

This normalizes the requested path and resolves ../ sequences and symbolic links.

3. Validate the final path

if os.path.commonpath(
    [base_dir, requested_path]
) != base_dir:

This checks whether the requested file is located inside the permitted directory.

If the path escapes the directory, the application rejects the request.

For high-security applications, also account for filesystem race conditions, restrict access to regular files, and consider using an allowlist of file identifiers instead of accepting arbitrary paths.

6. How to Test This Vulnerability

You can test the vulnerable implementation in a local environment.

Start your Flask application:

python app.py

Try a normal request:

curl "http://localhost:5000/download?file=readme.txt"

Then test a traversal attempt against your local lab:

curl "http://localhost:5000/download?file=../../etc/passwd"

If the application returns the contents of /etc/passwd, the vulnerability is confirmed.

After implementing the fix, repeat the same request.

The application should return:

403 Forbidden

Always perform these tests on systems you own or have explicit authorization to assess.

7. Where Can Path Traversal Occur?

Path Traversal is not limited to download endpoints.

It can appear in several places:

Application featurePotential security issueFile download APIsUnauthorized file readingFile upload APIsWriting files outside intended directoriesZIP extractionZip Slip vulnerabilityImage processingUnauthorized file accessDocument managementExposure of private documentsLog viewerAccess to sensitive server logs

One particularly important related vulnerability is Zip Slip, where a malicious archive contains filenames with directory traversal sequences. During extraction, files can be written outside the intended extraction directory.

8. Key Security Takeaways

  • Never trust filenames received from users.
  • Do not assume os.path.join() provides path validation.
  • Resolve paths before validating them.
  • Verify that the final path remains inside the permitted directory.
  • Avoid exposing internal exception messages to users.
  • Apply least-privilege filesystem permissions.
  • Prefer allowlisted file identifiers over arbitrary filesystem paths.
  • Test file upload, download, and archive extraction functionality.

Conclusion

Path Traversal is a good example of how a small coding mistake can become a serious security vulnerability.

The vulnerable Flask application did not have a complex security flaw. It simply trusted a filename supplied by the user without validating the final filesystem path.

Understanding these vulnerabilities at the code level is an important step toward building secure backend applications.

Security is not just about protecting APIs. It is also about understanding how your application interacts with the underlying operating system.

Building, breaking, and securing real systems — one vulnerability at a time.