文件系统安全

目录

PHP 受到大多数服务器系统中内置的安全性的影响,这些安全性涉及文件和目录的权限。这允许您控制文件系统中哪些文件可以被读取。对于所有拥有该文件系统访问权限的用户来说,应谨慎对待所有对世界可读的文件,以确保它们对读取是安全的。

由于 PHP 被设计为允许用户级访问文件系统,因此完全有可能编写一个 PHP 脚本,该脚本允许您读取系统文件,例如 /etc/passwd,修改您的以太网连接,发送大量打印作业等等。这有一些明显的影响,因为您需要确保您读写的是适当的文件。

考虑以下脚本,用户在其中指示他们希望删除其主目录中的文件。这假设一种情况,即 PHP Web 界面经常用于文件管理,因此 Apache 用户被允许删除用户主目录中的文件。

示例 #1 糟糕的变量检查会导致....

<?php
// 从用户的 home 目录中移除一个文件
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

unlink("$homedir/$userfile");

echo
"文件已删除!";
?>
由于用户名和文件名可以通过用户表单提交,因此用户可以提交属于其他人的用户名和文件名,并将其删除,即使他们不应该被允许这样做。在这种情况下,您需要使用其他形式的身份验证。考虑一下,如果提交的变量是 “../etc/” 和 “passwd”,会发生什么。然后,代码将有效地读取

示例 #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 "文件已删除!";
?>
您应该采取两项重要措施来防止这些问题。
  • 仅允许 PHP Web 用户二进制文件有限的权限。
  • 检查所有提交的变量。
这里是一个改进后的脚本

示例 #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 = "已删除 $filepath\n";
} else {
$logstring = "删除 $filepath 失败\n";
}
$fp = fopen("/home/logging/filedelete.log", "a");
fwrite($fp, $logstring);
fclose($fp);

echo
htmlentities($logstring, ENT_QUOTES);

?>
但是,即使这样也不是没有缺陷。如果您的身份验证系统允许用户创建自己的用户登录名,而用户选择了登录名 “../etc/”,则系统再次暴露。因此,您可能更愿意编写一个更自定义的检查

示例 #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(
"错误的用户名/文件名");
}

//等等...
?>

根据您的操作系统,有很多您应该注意的文件,包括设备条目 (/dev/ 或 COM1)、配置文件 (/etc/ 文件和 .ini 文件)、众所周知的存储区域 (/home/、我的文档)等等。因此,通常更容易创建一个策略,禁止除您明确允许的以外的所有内容。

添加说明

用户贡献的说明 7 个说明

匿名
18 年前
(A) 最好不要使用用户提供的名称创建文件或文件夹。如果您没有充分验证,就会遇到麻烦。相反,使用随机生成的名称创建文件和文件夹,例如 fg3754jk3h,并将用户名和此文件名存储在一个名为 user_objects 的表中。这将确保无论用户输入什么,发送到 shell 的命令将仅包含来自特定集合的值,并且不会造成任何破坏。

(B) 对基于用户选择的操作执行的命令也适用。最好不要让用户输入的任何部分进入要执行的命令。相反,保持一组固定的命令,并根据用户的输入来运行这些命令。

例如,
(A) 保持一个名为 user_objects 的表,其中包含以下值:
用户名 | 选择的名称 | 实际名称 | 文件或目录
--------|--------------|-----------|-----------
jdoe | trekphotos | m5fg767h67 | D
jdoe | notes.txt | nm4b6jh756 | F
tim1997 | _imp_ folder | 45jkh64j56 | D

并且始终在文件系统操作中使用 actual_name 而不是用户提供的名称。

(B)
<?php
$op
= $_POST['op'];//经过大量验证后
$dir = $_POST['dirname'];//经过大量验证,或者你可以使用技术 (A)
switch($op){
case
"cd":
chdir($dir);
break;
case
"rd":
rmdir($dir);
break;
.....
default:
mail("[email protected]", "Mischief", $_SERVER['REMOTE_ADDR']." is probably attempting an attack.");
}
fmrose at ncsu dot edu
18 年前
这里的所有修复都假设允许用户输入系统敏感信息本身是必要的。正确的方法是提供类似于文件的编号列表来执行 unlink 操作,然后用户选择匹配的编号。用户无法指定巧妙的攻击来绕过你可能具有的任何模式匹配文件名排除语法。

无论何时遇到安全问题,正确的行为都是拒绝所有,然后允许特定情况,而不是允许所有,然后限制。原因很简单,你可能没有想到所有可能的限制。
devik at cdi dot cz
22 年前
嗯,所有用户都在同一个 UID 下运行这一事实本身就是一个大问题。用户空间安全漏洞(如 safe_mode)不应该取代适当的内核级安全检查/记账。
好消息:Apache 2 允许你为不同的 vhosts 分配 UID。
devik
Latchezar Tzvetkoff
15 年前
可以通过 realpath() 对基本文件名/目录/符号链接进行检查(我个人会这样做)...

<?php

if (isset($_GET['file'])) {
$base = '/home/polizei/public_html/'; //看起来这个应该是realpath,意味着它不是符号链接路径..
if (strpos($file = realpath($base.$_GET['file']), $base) === 0 && is_file($file)) {
unlink($file);
} else {
die(
'blah!');
}
}
?>
cronos586(AT)caramail(DOT)com
22 年前
使用 Apache 时,你可能需要考虑对路径使用 apache_lookup_uri,以发现真实路径,无论使用任何目录技巧。
然后,查看前缀,并与允许的前缀列表进行比较。
例如,我的网站的 source.php 包含
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);
}
}
}
希望这有帮助
问候,
KAT44
1 at 234 dot cx
19 年前
我认为 Jones 在 partykel 中给出的文件名验证解决方案不完整。它当然有所帮助,但它没有解决用户能够从其主目录创建指向根目录的符号链接的情况。他随后可能要求取消链接“foo/etc/passwd”,这将在他的主目录中,但 foo 是一个指向 / 的符号链接。

我个人不会确信任何解决此问题的方案都能保证我的系统安全。以 root 身份运行 PHP(或任何能够在所有用户主目录中取消链接文件的等效方法)是在自找麻烦。

如果你有一个多用户系统,并且担心用户可能会安装这样的脚本,请尝试使用安全增强型 Linux。它不会提供完全保护,但至少可以确保不安全的用户脚本只能影响 Web 服务器有权访问的文件。无论有人安装什么脚本,外部人员都将无法读取你的密码文件——或删除它。
eLuddite at not dot a dot real dot addressnsky dot ru
23 年前
我认为教训很清楚

(1) 禁止用户名中的路径分隔符。
(2) 将用户名映射到物理主目录 - /home/username 就可以了
(3) 读取主目录
(4) 仅将 (3) 的结果作为删除选项呈现。

我发现了一种用 php 执行上述操作的奇妙方法,但这个提交框太小,无法容纳它。

:-)
To Top