views:

496

answers:

2

I want to make sure a file path set via query string does not go outside of the desired subdirectory. Right now, I am checking that:

  1. The path does not start with "/", to prevent the user from giving an absolute path.
  2. The path does not contain "..", to prevent the user from giving a path that is outside of the desired subdirectory.
  3. The path does not contain ":", to prevent the use of a url (i.e. "http://", "ftp://", etc.). Should I ever run this script on a Windows server (not likely), this will also prevent absolute paths beginning with a drive specifier (i.e. "C:\"). Note: I'm aware that a colon is a valid character in a Unix filenames, but I will never be using it in a filename.
  4. The path does not start with "\". Just in case I change my mind about running on a Windows server, this prevents Windows network paths from being specified (i.e. "\\someserver\someshare"). Again, I'm aware that a backslash is a valid Unix filename character, but I also won't be using it in any filenames.

Are these checks sufficient?

Background

I have a PHP script that takes (via query string) the path to a sample source file to be shown to a user. So I might give them a link like "view_sample.php?path=accounting_app/report_view.php" or "view_sample.php?path=ajax_demo/get_info.js".

The script looks basically like this:

$path = $_GET['path'];
if(path_is_valid($path) && is_file("sample/$path"))
{
  header('Content-Type: text/plain');
  readfile("sample/$path");
}

My concern is that a malicious user would see the url and try to do something like "view_sample.php?path=../../database/connection_info.php" and gain access to a file which is not in the "sample" directory.

Are the four checks I defined above (which would be implemented in the path_is_valid() function) sufficient to lock out a malicious user? (Also, I think checks 1, 3, and 4 are basically irrelevant since I am prepending a relative path, but if I didn't do this would the checks be sufficient?)

+6  A: 

Call

$path = realpath("sample/$path");

Then check that the resulting path starts with the directory you're expecting.

sth
NO, realpath does no such thing. realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input path . and return the canonicalized absolute pathname. http://ca.php.net/manual/en/function.realpath.php
Samuel
Isn't that what's needed (minus the symbolic links resolution maybe)? If you have the path in a canonicalized form you can easily find which directory it really references (check if it starts with '/var/www/data/' or wherever your data is).
sth
actually this sounds like it will do what i'm wanting.. if somehow the user specifies a relative path that's still in the sample directory, i really don't care (i.e. "../sample/.././sample/./.././sample/thefile.php").
Kip
+2  A: 
<?php
    // Current path information
    $path = $_GET['path'];
    $vroot = "sample";

    // Validate that the $path is a subfolder of $vroot
    $vroot = realpath($vroot);
    if(substr(realpath($path), 0, strlen($vroot)) != $vroot or !is_dir($path)) {lid!
        exit("Invalid path");
    } else {
       echo "Ah, everything is alright!";
    }
?>
Evan Fosmark