Skip to content

File Path Injection

What does this mean ?

A path traversal attack (also known as directory traversal) aims to access files and directories that are stored outside the web root folder. By manipulating variables that reference files with “dot-dot-slash (../)” sequences and its variations or by using absolute file paths, it may be possible to access arbitrary files and directories stored on file system including application source code or configuration and critical system files. This attack is also known as “dot-dot-slash”, “directory traversal”, “directory climbing” and “backtracking”.

What can happen ?

An attacker might be able to write to arbitrary files on the server, allowing them to modify application data or behavior, and ultimately take full control of the server.

Recommendation

The most effective way to prevent file path traversal vulnerabilities is to avoid passing user-supplied input to filesystem APIs altogether. Many application functions that do this can be rewritten to deliver the same behavior in a safer way. If it is considered unavoidable to pass user-supplied input to filesystem APIs, then two layers of defense should be used together to prevent attacks: The application should validate the user input before processing it. Ideally, the validation should compare against a whitelist of permitted values. If that isn't possible for the required functionality, then the validation should verify that the input contains only permitted content, such as purely alphanumeric characters. After validating the supplied input, the application should append the input to the base directory and use a platform filesystem API to canonicalize the path. It should verify that the canonicalized path starts with the expected base directory.

Sample Code

Vulnerable :

using System;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationDotNetCore.Controllers
{
    public class RSPEC2083IOInjectionNoncompliantController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult DeleteFile(string fileName)
        {
            System.IO.File.Delete(fileName); // Noncompliant

            return Content("File " + fileName + " deleted");
        }
    }
}

Non Vulnerable :

using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationDotNetCore.Controllers
{
    public class RSPEC2083IOInjectionCompliantController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult DeleteFile(string fileName)
        {
            string destDirectory = "~/CustomersData/";

            string destFileName = Path.GetFullPath(System.IO.Path.Combine(destDirectory, fileName));
            string fullDestDirPath = Path.GetFullPath(destDirectory + Path.DirectorySeparatorChar);

            if (!destFileName.StartsWith(fullDestDirPath, StringComparison.Ordinal))
            {
                System.IO.File.Delete(destFileName); // Compliant
                return Content("File " + fileName + " deleted");
            } else
            {
                return BadRequest();
            }
        }

    }
}

Vulnerable :

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String file = request.getParameter("file");

    File fileUnsafe = new File(file);
    try {
      FileUtils.forceDelete(fileUnsafe); // Noncompliant
    }
    catch(IOException ex){
      System.out.println (ex.toString());
    }
}

Non Vulnerable :

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String file = request.getParameter("file");

    File fileUnsafe = new File(file);
    File directory = new File("/tmp/");

    try {
      if(FileUtils.directoryContains(directory, fileUnsafe)) {
        FileUtils.forceDelete(fileUnsafe); // Compliant
      }
    }
    catch(IOException ex){
      System.out.println (ex.toString());
    }
}

Vulnerable :

$userId = $_GET["userId"];
$fileUUID = $_GET["fileUUID"];

if ( $_SESSION["userId"] == $userId ) {
  unlink("/storage/" . $userId . "/" . $fileUUID); // Noncompliant
}

Non Vulnerable :

$userId = (int) $_GET["userId"];
$fileUUID = (int) $_GET["fileUUID"];

if ( $_SESSION["userId"] == $userId ) {
  unlink("/storage/" . $userId . "/" . $fileUUID);
}

References