第 5 章 文件系统安全

PHP 遵从大多数服务器系统中关于文件和目录权限的安全机制。这就使管理员可以控制哪些文件在文件系统内是可读的。必须特别注意的是全局的可读文件,并确保每一个有权限的用户对这些文件的读取动作都是安全的。

PHP 被设计为以用户级别来访问文件系统,所以完全有可能通过编写一段 PHP 代码来读取系统文件如 /etc/passwd,更改网络连接以及发送大量打印任务等等。因此必须确保 PHP 代码读取和写入的是合适的文件。

请看下面的代码,用户想要删除自己主目录中的一个文件。假设此情形是通过 web 界面来管理文件系统,因此 Apache 用户有权删除用户目录下的文件。

例 5.1. 不对变量进行安全检查会导致……

<?php
// &#20174;&#29992;&#25143;&#30446;&#24405;&#20013;&#21024;&#38500;&#25351;&#23450;&#30340;&#25991;&#20214;
$username = $_POST['user_submitted_name'];
$homedir = "/home/$username";
$file_to_delete = "$userfile";
unlink ("$homedir/$userfile");
echo
"$file_to_delete has been deleted!";
?>

既然 username 变量可以通过用户表单来提交,那就可以提交别人的用户名和文件名,并删除该文件。这种情况下,就要考虑其它方式的认证。想想看如果提交的变量是“../etc/”和“passwd”会发生什么。上面的代码就等同于:

例 5.2. ……文件系统攻击

<?php
// &#21024;&#38500;&#30828;&#30424;&#20013;&#20219;&#20309; PHP &#26377;&#35775;&#38382;&#26435;&#38480;&#30340;&#25991;&#20214;&#12290;&#22914;&#26524; PHP &#26377; root &#26435;&#38480;&#65306;
$username = "../etc/";
$homedir = "/home/../etc/";
$file_to_delete = "passwd";
unlink ("/home/../etc/passwd");
echo
"/home/../etc/passwd has been deleted!";
?>

有两个重要措施来防止此类问题。

  • 只给 PHP 的 web 用户很有限的权限。
  • 检查所有提交上来的变量。

下面是改进的脚本:

例 5.3. 更安全的文件名检查

<?php
// &#21024;&#38500;&#30828;&#30424;&#20013; PHP &#26377;&#26435;&#35775;&#38382;&#30340;&#25991;&#20214;
$username = $_SERVER['REMOTE_USER']; // &#20351;&#29992;&#35748;&#35777;&#26426;&#21046;

$homedir = "/home/$username";

$file_to_delete = basename("$userfile"); // &#21435;&#38500;&#21464;&#37327;&#20013;&#30340;&#36335;&#24452;
unlink ($homedir/$file_to_delete);

$fp = fopen("/home/logging/filedelete.log","+a"); // &#35760;&#24405;&#21024;&#38500;&#21160;&#20316;
$logstring = "$username $homedir $file_to_delete";
fwrite ($fp, $logstring);
fclose($fp);

echo
"$file_to_delete has been deleted!";
?>

然而,这样做仍然是有缺陷的。如果认证系统允许用户建立自己的登录用户名,而用户选择用“../etc/”作为用户名,系统又一次沦陷了。所以,需要加强检查:

例 5.4. 更安全的文件名检查

<?php
$username
= $_SERVER['REMOTE_USER']; // &#20351;&#29992;&#35748;&#35777;&#26426;&#21046;
$homedir = "/home/$username";

if (!
ereg('^[^./][^/]*$', $userfile))
    die(
'bad filename'); // &#20572;&#27490;&#25191;&#34892;&#20195;&#30721;

if (!ereg('^[^./][^/]*$', $username))
    die(
'bad username'); // &#20572;&#27490;&#25191;&#34892;&#20195;&#30721;
//&#21518;&#30053;&#8230;&#8230;
?>

根据操作系统的不同,存在着各种各样需要注意的文件,包括联系到系统的设备(/dev/ 或者 COM1)、配置文件(/ect/ 文件和 .ini文件)、常用的存储区域(/home/ 或者 My Documents)等等。由于此原因,建立一个策略禁止所有权限而只开放明确允许的通常更容易些。