PHP Conference Japan 2024

文件系统安全

目录

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
"The file has been deleted!";

?>
由于用户名和文件名可以通过用户表单提交,因此他们可以提交属于其他人的用户名和文件名,并删除它,即使他们不应该被允许这样做。在这种情况下,您需要使用其他某种形式的身份验证。考虑如果提交的变量是 "../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 "The file has been deleted!";

?>
您应该采取两项重要措施来防止这些问题。
  • 仅允许 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 = "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);

?>
但是,即使这样也不是没有缺陷。如果您的身份验证系统允许用户创建自己的用户登录名,并且用户选择了登录名 "../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/My Documents)等。因此,通常更容易创建一项策略,禁止所有内容,除了您明确允许的内容。

添加注释

用户贡献的注释 5 条注释

99
匿名用户
19 年前
(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]", "恶意行为", $_SERVER['REMOTE_ADDR']." 可能正在尝试攻击。");
}
25
fmrose at ncsu dot edu
19 年前
此处的所有修复都假设允许用户首先输入系统敏感信息是必要的。处理此问题的正确方法是提供类似于要对其执行 unlink 操作的文件的编号列表,然后用户选择匹配的编号。用户无法指定任何巧妙的攻击来规避您可能具有的任何模式匹配文件名排除语法。

每当您遇到安全问题时,正确的行为是先拒绝所有内容,然后允许特定实例,而不是允许所有内容并进行限制。因为您可能无法想到所有可能的限制。
22
devik at cdi dot cz
23 年前
好吧,所有用户都以相同的 UID 运行这一事实是一个大问题。用户空间安全漏洞(如 safe_mode)不应替代适当的内核级安全检查/会计。
好消息:Apache 2 允许您为不同的虚拟主机分配 UID。
devik
10
Latchezar Tzvetkoff
15 年前
可以通过 realpath() 执行基本的 filename/directory/symlink 检查(我个人也这样做)…

<?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!');
}
}
?>
6
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
To Top