Skip to content

File Path Injection

What does this mean ?

A path traversal attack (also known as a directory traversal attack) attempts to get access to files and directories located outside the web root folder. It may be possible to access arbitrary files and directories stored on the file system, including application source code or configuration and crucial system files, by manipulating variables that reference files with "dot-dot-slash (../)" sequences and variants, or by employing absolute file paths. This attack is also referred to as "dot-dot-slash," "directory traversal," "directory climbing," and "backtracking."

What can happen ?

An attacker may be able to write to arbitrary files on the server, allowing them to change application data or behavior and eventually gain complete control of the server.

Recommendation

The most effective technique to avoid file path traversal vulnerabilities is to never transmit user-supplied information to filesystem APIs. Many application routines that perform this function can be redesigned to do the same job in a more secure manner. If passing user-supplied input to filesystem APIs is deemed inevitable, then two levels of security should be employed in tandem to prevent attacks: Before processing user inpus, the program should validate it. The validation should ideally check against a whitelist of acceptable values. If that isn't possible for the needed functionality, the validation should ensure that the input only contains approved material, such as entirely alphanumeric characters. After verifying the given input, the program should add the input to the base directory and canonicalize the path using a platform filesystem API. It should ensure that the canonicalized path begins with the anticipated 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);
}

Vulnerable :

s.Log("Directory Listing - /assets")
resp, err = httpClient.Get(ts.URL + "/assets")
assert.Nil(s, err)
assert.Equal(s, 200, resp.StatusCode)
body := responseBody(resp)
assert.True(s, strings.Contains(body, "<title>List of /assets/</title>"))
assert.True(s, strings.Contains(body, "<h1>List of /assets/</h1><hr>"))
assert.True(s, strings.Contains(body, `<a href="robots.txt">robots.txt</a>`))
assert.Equal(s, "", resp.Header.Get(ahttp.HeaderCacheControl))

Non Vulnerable :

func main() {
  c := filepath.Clean(
    "/sast/myapp/../../root/.ssh/authorized_keys"
  )
  fmt.Println("Cleaned path: " + c)
}

Vulnerable :

system(ls #{params[:project_id]})

Non Vulnerable :

system("ls", "params[:project_id]")

References