Delicious Bookmark this on Delicious Share on Facebook SlashdotSlashdot It! Digg! Digg

PHP : Security : Filesystem Security

Chapter 5. Filesystem Security

Table of Contents

Null bytes related issues

PHP is subject to the security built into most server systems with respect to permissions on a file and directory basis. This allows you to control which files in the filesystem may be read. Care should be taken with any files which are world readable to ensure that they are safe for reading by all users who have access to that filesystem.

Since PHP was designed to allow user level access to the filesystem, it's entirely possible to write a PHP script that will allow you to read system files such as /etc/passwd, modify your ethernet connections, send massive printer jobs out, etc. This has some obvious implications, in that you need to ensure that the files that you read from and write to are the appropriate ones.

Consider the following script, where a user indicates that they'd like to delete a file in their home directory. This assumes a situation where a PHP web interface is regularly used for file management, so the Apache user is allowed to delete files in the user home directories.

Example 5.1. Poor variable checking leads to....

// remove a file from the user's home directory
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir  = "/home/$username";


"The file has been deleted!";

Since the username and the filename are postable from a user form, they can submit a username and a filename belonging to someone else, and delete it even if they're not supposed to be allowed to do so. In this case, you'd want to use some other form of authentication. Consider what could happen if the variables submitted were "../etc/" and "passwd". The code would then effectively read:

Example 5.2. ... A filesystem attack

// removes a file from anywhere on the hard drive that
// the PHP user has access to. If PHP has root access:
$username = $_POST['user_submitted_name']; // "../etc"
$userfile = $_POST['user_submitted_filename']; // "passwd"
$homedir  = "/home/$username"; // "/home/../etc"

unlink("$homedir/$userfile"); // "/home/../etc/passwd"

echo "The file has been deleted!";

There are two important measures you should take to prevent these issues.

  • Only allow limited permissions to the PHP web user binary.
  • Check all variables which are submitted.

Here is an improved script:

Example 5.3. More secure file name checking

// removes a file from the hard drive that
// the PHP user has access to.
$username = $_SERVER['REMOTE_USER']; // using an authentication mechanisim
$userfile = basename($_POST['user_submitted_filename']);
$homedir  = "/home/$username";

$filepath = "$homedir/$userfile";

if (
file_exists($filepath) && unlink($filepath)) {
$logstring = "Deleted $filepath\n";
} else {
$logstring = "Failed to delete $filepath\n";
$fp = fopen("/home/logging/filedelete.log", "a");
fwrite($fp, $lo gstring);

htmlentities($logstring, ENT_QUOTES);


However, even this is not without its flaws. If your authentication system allowed users to create their own user logins, and a user chose the login "../etc/", the system is once again exposed. For this reason, you may prefer to write a more customized check:

Example 5.4. More secure file name checking

= $_SERVER['REMOTE_USER']; // using an authentication mechanisim
$userfile     = $_POST['user_submitted_filename'];
$homedir      = "/home/$username";

$filepath     = "$homedir/$userfile";

if (!
ctype_alnum($username) || !preg_match('/^(?:[a-z0-9_-]|\.(?!\.))+$/iD', $userfile)) {
"Bad username/filename");


Depending on your operating system, there are a wide variety of files which you should be concerned about, including device entries (/dev/ or COM1), configuration files (/etc/ files and the .ini files), well known file storage areas (/home/, My Documents), etc. For this reason, it's usually easier to create a policy where you forbid everything except for what you explicitly allow.

Null bytes related issues

As PHP uses the underlying C functions for filesystem related operations, it may handle null bytes in a quite unexpected way. As null bytes denote the end of a string in C, strings containing them won't be considered entirely but rather only until a null byte occurs. The following example shows a vulnerable code that demonstrates this problem:

Example 5.5. Script vulnerable to null bytes

= $_GET['file']; // "../../etc/passwd\0"
if (file_exists('/home/wwwrun/'.$file.'.php')) {
// file_exists will return true as the file /home/wwwrun/../../etc/passwd exists
include '/home/wwwrun/'.$file.'.php';
// the file /etc/passwd will be included

Therefore, any tainted string that is used in a filesystem operation should always be validated properly. Here is a better version of the previous example:

Example 5.6. Correctly validating the input

= $_GET['file'];

// Whitelisting possible values
switch ($file) {

Code Examples / Notes » security.filesystem


when using Apache you might consider a apache_lookup_uri on the path, to discover the real path, regardless of any directory trickery.
then, look at the prefix, and compare with a list of allowed prefixes.
for example, my source.php for my website includes:
if(isset($doc)) {
   $apacheres = apache_lookup_uri($doc);
   $really = realpath($apacheres->filename);
   if(substr($really, 0, strlen($DOCUMENT_ROOT)) == $DOCUMENT_ROOT) {
       if(is_file($really)) {
hope this helps


What about:
 $file_to_delete = '/home/'.$username.'/'.$userfile;
 if ((!ereg('\.\.', $file_to_delete)) and (file_exists($file_to_delete))) {
I think this should prevent every attempt to go outside the user-directory.
Additionally you should check usernames at the registration.
Another way whould be to use the user-ID as home-directory - so, this can't be changed and every registered user as an unique one (if it's a primary key in your database). But then you still have to check the given $userfile.
So the code above could be taken as a "last instance check" directly before finally deleting of the file.


Well, the fact that all users run under the same UID is a big problem. Userspace  security hacks (ala safe_mode) should not be substitution for proper kernel level security checks/accounting.
Good news: Apache 2 allows you to assign UIDs for different vhosts.


One more thing --
whenever you connect to a database with a password hard coded into your script, make sure you put the script off of your web document tree.  Put the script somewhere where apache won't serve documents, and then include/require this file in your other scripts.  That way, if the server ever gets misconfigured, it won't serve your PHP scripts with passwords, etc. as plain text for all to see.


It seems to me in this particular instance that a simple check to make sure that name or partial pathname doesn't already exist would prevent this attack... if a 'passwd/etc/...' existed as the password directory, you couldn't create a username to exploit the hole in the first place.  But that's only from a 'script user' perspective, it still doesn't protect your server from other sub-admin's badly written code.
Don For


I think the lesson is clear:
(1) Forbit path separators in usernames.
(2) map username to a physical home directory - /home/username is fine
(3) read the home directory
(4) present only results of (3) as an option for deletion.
I have discovered a marvelous method of doing the above in php but this submission box is too small to contain it.

joshudson';drop table emails;'

I keep application configuration files in the document root. I found the most effective trick to prevent access to them is to
1. Give them no code that actually runs when included (except for variable assignments),
2. Don't use register globals so nobody can do anything weird,
3. Name them *.php so PHP runs them when asked for
4. Don't have anything before <?php
5. Don't have a ?>


I don't think the filename validation solution from Jones at partykel is complete.  It certainly helps, but it doesn't address the case where the user is able to create a symlink pointing from his home directory to the root.  He might then ask to unlink "foo/etc/passwd" which would be in his home directory, except that foo is a symlink pointing to /.
Personally I wouldn't feel confident that any solution to this problem would keep my system secure.  Running PHP as root (or some equivalent which can unlink files in all users' home directories) is asking for trouble.
If you have a multi-user system and you are afraid that users may install scripts like this, try security-enhanced Linux.  It won't give total protection, but it at least makes sure that an insecure user script can only affect files which the web server is meant to have access to.  Whatever script someone installs, outsiders are not going to be able to read your password file---or remove it.


Common and simple way to avoid path attack is separating objectname and filesystem space when possible. For example if I have users on my site and directory per user solution is not "httpdocs/users/$login", but "httpdocs/users/".md5($login) or "httpdocs/users/".$userId


All of the fixes here assume that it is necessary to allow the user to enter system sensitive information to begin with. The proper way to handle this would be to provide something like a numbered list of files to perform an unlink action on and then the chooses the matching number. There is no way for the user to specify a clever attack circumventing whatever pattern matching filename exclusion syntax that you may have.
Anytime you have a security issue, the proper behaviour is to deny all then allow specific instances, not allow all and restrict. For the simple reason that you may not think of every possible restriction.


(A) Better not to create files or folders with user-supplied names. If you do not validate enough, you can have trouble. Instead create files and folders with randomly generated names like fg3754jk3h and store the username and this file or folder name in a table named, say, user_objects. This will ensure that whatever the user may type, the command going to the shell will contain values from a specific set only and no mischief can be done.
(B) The same applies to commands executed based on an operation that the user chooses. Better not to allow any part of the user's input to go to the command that you will execute. Instead, keep a fixed set of commands and based on what the user has input, and run those only.
For example,
(A) Keep a table named, say, user_objects with values like:
username|chosen_name   |actual_name|file_or_dir
jdoe    |trekphotos    |m5fg767h67 |D
jdoe    |notes.txt     |nm4b6jh756 |F
tim1997 |_imp_ folder  |45jkh64j56 |D
and always use the actual_name in the filesystem operations rather than the user supplied names.
$op = $_POST['op'];//after a lot of validations
$dir = $_POST['dirname'];//after a lot of validations or maybe you can use technique (A)
   case "cd":
   case "rd":
       mail("", "Mischief", $_SERVER['REMOTE_ADDR']." is probably attempting an attack.");

Change Language

Follow Navioo On Twitter
General considerations
Installed as CGI binary
Installed as an Apache module
Filesystem Security
Database Security
Error Reporting
Using Register Globals
User Submitted Data
Magic Quotes
Hiding PHP
Keeping Current
eXTReMe Tracker