Previous Section  < Day Day Up >  Next Section

10.7 Sanitizing Externally Supplied Filenames

Just like data submitted in a form or URL can cause problems when it is displayed (cross-site scripting attack) or put in an SQL query (SQL injection attack), it can also cause problems when it is used as a filename or as part of a filename. It doesn't have a fancy name like those other attacks, but it can be just as devastating.

The cause of the problem is the same: there are special characters that must be escaped so they lose their special meaning. In filenames, the special characters are / (which separates parts of filenames), and the two-character sequence .. (which means "go up one directory" in a filename).

For example, the funny-looking filename /usr/local/data/../../../etc/passwd doesn't point to a file under the /usr/local/data directory but instead to the file /etc/passwd, which, on most Unix systems, contains a list of user accounts. The filename /usr/local/data/../../../etc/passwd means "from the directory /usr/local/data, go up one level (to /usr/local), then go up another level (to /usr), then go up another level (to /, the top level of the filesystem), then down into /etc, then stop at the file passwd."

How could this be a problem in your PHP programs? When you use data from a form in a filename, you are vulnerable to this sort of attack unless you sanitize that submitted form data. Example 10-23 takes the approach of removing all forward slashes and .. sequences from a submitted form parameter before incorporating the parameter into a filename.

Example 10-23. Cleaning up a form parameter that goes in a filename
// Remove slashes from user

$user = str_replace('/', '', $_POST['user']);

// Remove .. from user

$user = str_replace('..', '', $user);

print 'User profile for ' . htmlentities($user) .': <br/>';

print file_get_contents("/usr/local/data/$user");

If a malicious user supplies ../../../etc/passwd as the user form parameter in Example 10-23, that is translated into etcpasswd before being interpolated into the filename used with file_get_contents( ).

Another helpful technique for getting rid of user-entered nastiness is to use realpath( ). It translates an obfuscated filename that contains .. sequences into the ..-less version of filename that more directly indicates where the file is. For example, realpath('/usr/local/data/../../../etc/passwd') returns the string /etc/passwd. You can use realpath( ) as in Example 10-24: to see whether filenames, after incorporating form data, are acceptable.

Example 10-24. Cleaning up a file name with realpath( )
$filename = realpath("/usr/local/data/$_POST[user]");

// Make sure that $filename is under /usr/local/data

if ('/usr/local/data/' =  = substr($filename, 0, 16)) {

    print 'User profile for ' . htmlentities($_POST['user']) .': <br/>';

    print file_get_contents($filename);

} else {

    print "Invalid user entered.";


In Example 10-24, if $_POST['user'] is james, then $filename is set to /usr/local/data/james and the if( ) code block runs. However, if $_POST['user'] is something suspicious such as ../secrets.txt, then $filename is /usr/local/secrets.txt, and the if( ) test fails, so Invalid user entered is printed.

    Previous Section  < Day Day Up >  Next Section