1
0
mirror of https://github.com/php/doc-zh.git synced 2026-03-23 22:52:08 +01:00
Files
archived-doc-zh/security/filesystem.xml

216 lines
6.7 KiB
XML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 91570644fbbe4d23e79908e1a04c4c4d003fe587 Maintainer: lm92 Status: ready -->
<!-- CREDITS: dallas, mowangjuanzi, Luffy -->
<chapter xml:id="security.filesystem" xmlns="http://docbook.org/ns/docbook">
<title>文件系统安全</title>
<simpara>
<acronym>PHP</acronym> 受大多数服务器系统中文件和目录权限的内置安全机制的影响。
这允许控制文件系统中哪些文件是可读的。应该小心对待任何全局可读的文件,
要确保所有有权限访问该文件系统的用户都可以安全的读取文件。
</simpara>
<simpara>
<acronym>PHP</acronym> 被设计为以用户级别访问文件系统,
因此完全可以编写 <acronym>PHP</acronym> 脚本来读取系统文件,例如 <filename>/etc/passwd</filename>
修改网络连接、发送大量打印任务等。这有一些明显的影响,因此需要确保读写的是合适的文件。
</simpara>
<simpara>
请看下面的脚本,用户表示想要删除自己主目录下的一个文件。
假设 <acronym>PHP</acronym> web 界面通常用于文件管理,
因此 Apache 用户允许删除用户主目录中的文件。
</simpara>
<para>
<example>
<title>不对变量检查会导致....</title>
<programlisting role="php">
<![CDATA[
<?php
// 从用户主目录移除一个文件
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";
unlink("$homedir/$userfile");
echo "The file has been deleted!";
?>
]]>
</programlisting>
</example>
由于 username 和 filename 由用户表单中提交,那就能提交属于其他人的 username
和 filename甚至可以删除不被允许的文件。这种情况下
应该使用一些其它形式的身份验证。不妨考虑一下,如果提交的变量是 <literal>“../etc/”</literal>
<literal>“passwd”</literal> 会发生什么。上面代码将等同于:
<example>
<title>... 文件系统攻击</title>
<programlisting role="php">
<![CDATA[
<?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!";
?>
]]>
</programlisting>
</example>
有两个重要措施来防止此类问题。
<itemizedlist>
<listitem>
<simpara>
<acronym>PHP</acronym> web 用户二进制文件仅允许有限的权限。
</simpara>
</listitem>
<listitem>
<simpara>
检查所有提交上来的变量。
</simpara>
</listitem>
</itemizedlist>
这是改进的脚本:
<example>
<title>更安全的文件名检查</title>
<programlisting role="php">
<![CDATA[
<?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);
?>
]]>
</programlisting>
</example>
然而,这样做也是有缺陷的。如果认证系统允许用户创建自己的登录用户名,
而用户选择 <literal>“../etc/”</literal> 作为登录名,系统将再次暴露。
出于这个原因,需要编写自定义检查:
<example>
<title>更安全的文件名检查</title>
<programlisting role="php">
<![CDATA[
<?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");
}
//等等...
?>
]]>
</programlisting>
</example>
</para>
<para>
根据操作系统的不同,需要关心各种各样的文件,比如设备条目(<filename>/dev/</filename>
<filename>COM1</filename>)、配置文件(<filename>/etc/</filename> 文件和 <literal>.ini</literal> 文件)、
众所周知的文件存储区域(<filename>/home/</filename><filename>My Documents</filename>)等等。出于这个原因,
创建一个禁止所有权限而只开放明确允许的策略通常更容易些。
</para>
<sect1 xml:id="security.filesystem.nullbytes">
<title>空字符Null bytes相关问题</title>
<simpara>
由于 <acronym>PHP</acronym> 使用底层 C 函数进行文件系统相关操作,因此它可能会以完全意想不到的方式处理空字符。
在 C 语言中空字符标识字符串的结束,所以一个完整的字符串是从其开头到遇见空字符为止。
下面的示例展示了易受攻击的代码,演示了这个问题:
</simpara>
<example>
<title>会被空字符攻击的代码</title>
<programlisting role="php">
<![CDATA[
<?php
$file = $_GET['file']; // "../../etc/passwd\0"
if (file_exists('/home/wwwrun/' . $file . '.php')) {
// 当 /home/wwwrun/../../etc/passwd 文件存在的时候 file_exists 将会返回 true
include '/home/wwwrun/' . $file . '.php';
// 将会加载 /etc/passwd 文件
}
?>
]]>
</programlisting>
</example>
<para>
因此,在文件系统操作中都应该正确检查任何被污染的字符串。这是上个示例的改进版本:
</para>
<example>
<title>验证输入的正确性</title>
<programlisting role="php">
<![CDATA[
<?php
$file = $_GET['file'];
// 对值进行白名单检查
switch ($file) {
case 'main':
case 'foo':
case 'bar':
include '/home/wwwrun/include/' . $file . '.php';
break;
default:
include '/home/wwwrun/include/main.php';
}
?>
]]>
</programlisting>
</example>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->