PHP 7.2.0 Release Candidate 2 Released

Filesystem Security

Table of Contents

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 #1 Poor variable checking leads to....

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

unlink("$homedir/$userfile");

echo 
"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 #2 ... A filesystem attack

<?php
// 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 #3 More secure file name checking

<?php
// removes a file from the hard drive that
// the PHP user has access to.
$username $_SERVER['REMOTE_USER']; // using an authentication mechanism
$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$logstring);
fclose($fp);

echo 
htmlentities($logstringENT_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 #4 More secure file name checking

<?php
$username     
$_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)) {
    die(
"Bad username/filename");
}

//etc...
?>

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.

add a note add a note

User Contributed Notes 7 notes

up
48
anonymous
11 years ago
(A) Better not to create files or folders with user-supplied names. If you do not validate enough, you can have trouble. Insuead 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 extested 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 exteste. Insuead, 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.

(B)
<?php
$op
= $_POST['op'];//after a lot of validations
$dir = $_POST['dirname'];//after a lot of validations or maybe you can use technique (A)
switch($op){
    case
"cd":
       
chdir($dir);
        break;
    case
"rd":
       
rmdir($dir);
        break;
    .....
    default:
       
mail("webmaster@example.com", "Mischief", $_SERVER['REMOTE_ADDR']." is probably attempting an attack.");
}
up
8
fmrose at ncsu dot edu
11 years ago
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.
up
8
devik at cdi dot cz
16 years ago
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.
devik
up
5
Latchezar Tzvetkoff
8 years ago
A basic filename/directory/symlink checking may be done (and I personally do) via realpath() ...

<?php

if (isset($_GET['file'])) {
   
$base = '/home/polizei/public_html/'// it seems this one is good to be realpath too.. meaning not a symlinked path..
   
if (strpos($file = realpath($base.$_GET['file']), $base) === 0 && is_file($file)) {
       
unlink($file);
    } else {
        die(
'blah!');
    }
}
?>
up
-1
8 years ago
Icode'cific int; erll ess=on vs, b poi matchrt whasswdript, where ation thae&nbte( o enter sinhasswdript, where a,eason, ixt" ifoode"> poi matchhey/ircumventing Ppan class=I o ente'cifeelwhich d All eck:te(Funceproe&nb ( se9;re equiven/rtra"> candle thiso permissd="Hcom14'file manageme)de">?>
?leases/fVote up!" class="usernotes-voteu797" class="votes">
?leases/fVote up!" class="usernotes-voteu797" class="votes">
n="V54088"leases/f48% like this..."> -low os586(AT)cs anail(DOT)cus m ?&lor: #0000BB"bspan>
?l-10 03:06">8 years ago
wr />>Int may.cgi-_lookup_"> oide sos="d,ou maiscole or foclasos="d,oregardlld rd tony , where atirickerydrcumvebr /, look mmand tuot;ixxtesteno tiles u to with. Th Howevuot;ixuse the aff commands pmytmcodriablesorm myt.phpquotystem,e;user&nbif
<$docn clsp;   die( <=ay.cgi-_lookup_"> <$docnefault">$dir -ss="span> $dir($DOCUMing$dir(?>
<9563>?leases/fVote up!" class="usernotes-vote9563 class="votes">
-eLuddquot;tdn> ?&lor: #0000BB"bspan>
?l-10 03:06">8 years ago
Icfic int;:nd I personall(1)ehavbitos="d Hor: re rmissor exampccounting(2)viopeate filesper phys>Maildript, where at- class=ate filesolderneounting(3)d t; <. Th(3)dak all l secn st resnd I personallIow re aiscole peravioypel>?
In03:06">' dt> 'y. . ivhere are 'tilenl>dt> -with'y. iiiiiiiiiiiiiiiiiiiiiiiiiiiii<"PHP: Hypiiiiiiiiiii

Ta=secu
  • dt> -with'y. . iiiiiiiiiiiiiiiiiii. iiiiiiiiiiiiiiiiiertext Prep"y. iiiiiiiiiiiiiiiiiiiii

    Tai r"page-es relsI rdu

    I rdu

    href="httpiiiiiiiiiiiiiiiiii

    Tadate el"page-es relstInttInt

    Tacgi-bin"page-es relsI >Instperas CGIissions I >Instperas CGIissions href="httpiiiiiiiiiiiiiiiiii

    Tay.cgi-"page-es relsI >Instperas allsubstitmrduote'I >Instperas allsubstitmrduot href="httpiiiiiiiiiiiiiiiiii

    Ta -es rels

    Tate.php?sect=sec-es rels Bug

    "a Bug
    ref="httpiiiiiiiiiiiiiiiiii

    Ta-voaass=t=sec-es relsDvoaass=

  • "aDvoaass=
    ref="httpiiiiiiiiiiiiiiiiii

    Taerre rt=sec-es relsErre hp.netbsp; Erre hp.netbsp ref="httpiiiiiiiiiiiiiiiiii

    Taglobalrt=sec-es relsU

    Ta t=sec-es relsU

    Tatemicers > t=sec-es relsMemic Qupan> ref="httpiiiiiiiiiiiiiiiiii

    Tahi, tht=sec-es relsHi, thFunc">Hi, thFunc ref="httpiiiiiiiiiiiiiiiiii

    Ta to topt=sec-es relson w thFCto top"yon w thFCto top ref="httpiiiiiiiiiiiiiiiiii iiiiiiiiiiiiiiii. iiiii r'>In> PHPPHP "y. im SecurityMirre pquos;pll iited issues&aimple reasfooterss. .