Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.


Description

Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin", which may also be useful in accessing unexpected files. This is referred to as absolute path traversal.

In many programming languages, the injection of a null byte (the 0 or NUL) may allow an attacker to truncate a generated filename to widen the scope of attack. For example, the product may add ".txt" to any pathname, thus limiting the attacker to text files, but a null injection may effectively remove this restriction.

Demonstrations

The following examples help to illustrate the nature of this weakness and describe methods or techniques which can be used to mitigate the risk.

Note that the examples here are by no means exhaustive and any given weakness may have many subtle varieties, each of which may require different detection methods or runtime controls.

Example One

The following code could be for a social networking application in which each user's profile information is stored in a separate file. All files are stored in a single directory.

my $dataPath = "/users/cwe/profiles";
my $username = param("user");
my $profilePath = $dataPath . "/" . $username;

open(my $fh, "<", $profilePath) || ExitError("profile read error: $profilePath");
print "<ul>\n";
while (<$fh>) {
  print "<li>$_</li>\n";
}
print "</ul>\n";

While the programmer intends to access files such as "/users/cwe/profiles/alice" or "/users/cwe/profiles/bob", there is no verification of the incoming user parameter. An attacker could provide a string such as:

../../../etc/passwd

The program would generate a profile pathname like this:

/users/cwe/profiles/../../../etc/passwd

When the file is opened, the operating system resolves the "../" during path canonicalization and actually accesses this file:

/etc/passwd

As a result, the attacker could read the entire text of the password file.

Notice how this code also contains an error message information leak (CWE-209) if the user parameter does not produce a file that exists: the full pathname is provided. Because of the lack of output encoding of the file that is retrieved, there might also be a cross-site scripting problem (CWE-79) if profile contains any HTML, but other code would need to be examined.

Example Two

In the example below, the path to a dictionary file is read from a system property and used to initialize a File object.

String filename = System.getProperty("com.domain.application.dictionaryFile");
File dictionaryFile = new File(filename);

However, the path is not validated or modified to prevent it from containing relative or absolute path sequences before creating the File object. This allows anyone who can control the system property to determine what file is used. Ideally, the path should be resolved relative to some kind of application or user home directory.

Example Three

The following code takes untrusted input and uses a regular expression to filter "../" from the input. It then appends this result to the /home/user/ directory and attempts to read the file in the final resulting path.

my $Username = GetUntrustedInput();
$Username =~ s/\.\.\///;
my $filename = "/home/user/" . $Username;
ReadAndSendFile($filename);

Since the regular expression does not have the /g global match modifier, it only removes the first instance of "../" it comes across. So an input value such as:

../../../etc/passwd

will have the first "../" stripped, resulting in:

../../etc/passwd

This value is then concatenated with the /home/user/ directory:

/home/user/../../etc/passwd

which causes the /etc/passwd file to be retrieved once the operating system has resolved the ../ sequences in the pathname. This leads to relative path traversal (CWE-23).

Example Four

The following code attempts to validate a given input path by checking it against an allowlist and once validated delete the given file. In this specific case, the path is considered valid if it starts with the string "/safe_dir/".

String path = getInputPath();
if (path.startsWith("/safe_dir/"))
{
  File f = new File(path);
  f.delete()
}

An attacker could provide an input such as this:

/safe_dir/../important.dat

The software assumes that the path is valid because it starts with the "/safe_path/" sequence, but the "../" sequence will cause the program to delete the important.dat file in the parent directory

Example Five

The following code demonstrates the unrestricted upload of a file with a Java servlet and a path traversal vulnerability. The action attribute of an HTML form is sending the upload file request to the Java servlet.

<form action="FileUploadServlet" method="post" enctype="multipart/form-data">

Choose a file to upload:
<input type="file" name="filename"/>
<br/>
<input type="submit" name="submit" value="Submit"/>

</form>

When submitted the Java servlet's doPost method will receive the request, extract the name of the file from the Http request header, read the file contents from the request and output the file to the local upload directory.

public class FileUploadServlet extends HttpServlet {


  ...

  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    String contentType = request.getContentType();

    // the starting position of the boundary header
    int ind = contentType.indexOf("boundary=");
    String boundary = contentType.substring(ind+9);

    String pLine = new String();
    String uploadLocation = new String(UPLOAD_DIRECTORY_STRING); //Constant value

    // verify that content type is multipart form data
    if (contentType != null && contentType.indexOf("multipart/form-data") != -1) {


      // extract the filename from the Http header
      BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
      ...
      pLine = br.readLine();
      String filename = pLine.substring(pLine.lastIndexOf("\\"), pLine.lastIndexOf("\""));
      ...

      // output the file to the local upload directory
      try {

        BufferedWriter bw = new BufferedWriter(new FileWriter(uploadLocation+filename, true));
        for (String line; (line=br.readLine())!=null; ) {
          if (line.indexOf(boundary) == -1) {
            bw.write(line);
            bw.newLine();
            bw.flush();
          }
        } //end of for loop
        bw.close();



      } catch (IOException ex) {...}
      // output successful upload response HTML page

    }
    // output unsuccessful upload response HTML page
    else
    {...}

  }
    ...


}

This code does not perform a check on the type of the file being uploaded (CWE-434). This could allow an attacker to upload any executable file or other file with malicious code.

Additionally, the creation of the BufferedWriter object is subject to relative path traversal (CWE-23). Since the code does not check the filename that is provided in the header, an attacker can use "../" sequences to write to files outside of the intended directory. Depending on the executing environment, the attacker may be able to specify arbitrary files to write to, leading to a wide variety of consequences, from code execution, XSS (CWE-79), or system crash.

Example Six

This script intends to read a user-supplied file from the current directory. The user inputs the relative path to the file and the script uses Python's os.path.join() function to combine the path to the current working directory with the provided path to the specified file. This results in an absolute path to the desired file. If the file does not exist when the script attempts to read it, an error is printed to the user.

import os
import sys
def main():

  filename = sys.argv[1]
  path = os.path.join(os.getcwd(), filename)
  try:

    with open(path, 'r') as f:

      file_data = f.read()


  except FileNotFoundError as e:

    print("Error - file not found")


main()

However, if the user supplies an absolute path, the os.path.join() function will discard the path to the current working directory and use only the absolute path provided. For example, if the current working directory is /home/user/documents, but the user inputs /etc/passwd, os.path.join() will use only /etc/passwd, as it is considered an absolute path. In the above scenario, this would cause the script to access and read the /etc/passwd file.

import os
import sys
def main():

  filename = sys.argv[1]
  path = os.path.normpath(f"{os.getcwd()}{os.sep}{filename}")
  try:

    with open(path, 'r') as f:

      file_data = f.read()


  except FileNotFoundError as e:

    print("Error - file not found")


main()

The constructed path string uses os.sep to add the appropriate separation character for the given operating system (e.g. '\' or '/') and the call to os.path.normpath() removes any additional slashes that may have been entered - this may occur particularly when using a Windows path. By putting the pieces of the path string together in this fashion, the script avoids a call to os.path.join() and any potential issues that might arise if an absolute path is entered. With this version of the script, if the current working directory is /home/user/documents, and the user inputs /etc/passwd, the resulting path will be /home/user/documents/etc/passwd. The user is therefore contained within the current working directory as intended.

See Also

Comprehensive Categorization: File Handling

Weaknesses in this category are related to file handling.

OWASP Top Ten 2021 Category A01:2021 - Broken Access Control

Weaknesses in this category are related to the A01 category "Broken Access Control" in the OWASP Top Ten 2021.

CISQ Quality Measures - Security

Weaknesses in this category are related to the CISQ Quality Measures for Security. Presence of these weaknesses could reduce the security of the software.

Comprehensive CWE Dictionary

This view (slice) covers all the elements in CWE.

Weaknesses in the 2023 CWE Top 25 Most Dangerous Software Weaknesses

CWE entries in this view are listed in the 2023 CWE Top 25 Most Dangerous Software Weaknesses.

Weaknesses in the 2022 CWE Top 25 Most Dangerous Software Weaknesses

CWE entries in this view are listed in the 2022 CWE Top 25 Most Dangerous Software Weaknesses.


Common Weakness Enumeration content on this website is copyright of The MITRE Corporation unless otherwise specified. Use of the Common Weakness Enumeration and the associated references on this website are subject to the Terms of Use as specified by The MITRE Corporation.