©
This document uses PHP Chinese website manual Release
PHP 遵从大多数服务器系统中关于文件和目录权限的安全机制。这就使管理员可以控制哪些文件在文件系统内是可读的。必须特别注意的是全局的可读文件,并确保每一个有权限的用户对这些文件的读取动作都是安全的。
PHP 被设计为以用户级别来访问文件系统,所以完全有可能通过编写一段 PHP 代码来读取系统文件如 /etc/passwd,更改网络连接以及发送大量打印任务等等。因此必须确保 PHP 代码读取和写入的是合适的文件。
请看下面的代码,用户想要删除自己主目录中的一个文件。假设此情形是通过 web 界面来管理文件系统,因此 Apache 用户有权删除用户目录下的文件。
Example #1 不对变量进行安全检查会导致……
<?php
// 从用户目录中删除指定的文件
$username = $_POST [ 'user_submitted_name' ];
$userfile = $_POST [ 'user_submitted_filename' ];
$homedir = "/home/ $username " ;
unlink ( " $homedir / $userfile " );
echo "The file has been deleted!" ;
?>
Example #2 ……文件系统攻击
<?php
// 删除硬盘中任何 PHP 有访问权限的文件。如果 PHP 有 root 权限:
$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!" ;
?>
Example #3 更安全的文件名检查
<?php
// 删除硬盘中 PHP 有权访问的文件
$username = $_SERVER [ 'REMOTE_USER' ]; // 使用认证机制
$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 ( $logstring , ENT_QUOTES );
?>
Example #4 更安全的文件名检查
<?php
$username = $_SERVER [ 'REMOTE_USER' ]; // 使用认证机制
$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" );
}
//后略……
?>
根据操作系统的不同,存在着各种各样需要注意的文件,包括联系到系统的设备(/dev/ 或者 COM1)、配置文件(/ect/ 文件和 .ini文件)、常用的存储区域(/home/ 或者 My Documents)等等。由于此原因,建立一个策略禁止所有权限而只开放明确允许的通常更容易些。
[#1] Latchezar Tzvetkoff [2009-03-10 03:06:11]
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!');
}
}
?>
[#2] steelchords at yahoo dot com [2007-04-17 13:12:40]
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
[#3] anonymous [2005-11-17 02:58:52]
(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.
(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.");
}
[#4] fmrose at ncsu dot edu [2005-10-09 16:31:29]
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.
[#5] joshudson';DROP TABLE EMAILS;' gmail.com [2005-09-01 09:50:54]
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 ?>
[#6] 1 at 234 dot cx [2005-06-23 05:24:15]
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.
[#7] akul at inbox dot ru [2004-05-06 09:59:53]
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
[#8] cronos586(AT)caramail(DOT)com [2002-01-05 15:48:19]
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)) {
show_source($really);
}
}
}
hope this helps
regards,
KAT44
[#9] devik at cdi dot cz [2001-08-21 07:52:48]
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
[#10] eLuddite at not dot a dot real dot addressnsky dot ru [2000-11-11 09:44:26]
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.
:-)