Just as 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. This problem 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 in the /usr/local/data directory but instead to the location of 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 an attack that enables a user to gain access to areas of your filesystem that you may not have intended, unless you sanitize the submitted form data. Example 9-21 takes the approach of removing all forward slashes and .. sequences from a submitted form parameter before incorporating the parameter into a filename.
Example 9-21. 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);
if (is_readable(“/usr/local/data/$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 9-21, 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 the 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 9-22 to see whether filenames, after incorporating form data, are acceptable.
Example 9-22. Cleaning up a filename 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))
&& is_readable($filename)) {
print ‘User profile for ‘ . htmlentities($_POST[‘user’]) .’: <br/>’;
print file_get_contents($filename);
} else {
print “Invalid user entered.”;
}
In Example 9-22, 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 set to /usr/local/ secrets.txt, and the if() test fails, so Invalid user entered. is printed.
Source: Sklar David (2016), Learning PHP: A Gentle Introduction to the Web’s Most Popular Language, O’Reilly Media; 1st edition.