<?php
include_once $_SERVER['DOCUMENT_ROOT'] . '/include/shared-manual.inc';
$TOC = array();
$TOC_DEPRECATED = array();
$PARENTS = array();
include_once dirname(__FILE__) ."/toc/security.inc";
$setup = array (
  'home' => 
  array (
    0 => 'index.php',
    1 => 'PHP Manual',
  ),
  'head' => 
  array (
    0 => 'UTF-8',
    1 => 'zh',
  ),
  'this' => 
  array (
    0 => 'security.filesystem.php',
    1 => '文件系统安全',
    2 => '文件系统安全',
  ),
  'up' => 
  array (
    0 => 'security.php',
    1 => '安全',
  ),
  'prev' => 
  array (
    0 => 'security.sessions.php',
    1 => '会话（Session）安全',
  ),
  'next' => 
  array (
    0 => 'security.filesystem.nullbytes.php',
    1 => '空字符（Null bytes）相关问题',
  ),
  'alternatives' => 
  array (
  ),
  'source' => 
  array (
    'lang' => 'zh',
    'path' => 'security/filesystem.xml',
  ),
  'history' => 
  array (
  ),
  'extra_header_links' => 
  array (
    'rel' => 'alternate',
    'href' => '/manual/en/feeds/security.filesystem.atom',
    'type' => 'application/atom+xml',
  ),
);
$setup["toc"] = $TOC;
$setup["toc_deprecated"] = $TOC_DEPRECATED;
$setup["parents"] = $PARENTS;
manual_setup($setup);

contributors($setup);

?>
<div id="security.filesystem" class="chapter">
   <h1 class="title">文件系统安全</h1>
<h2>目录</h2><ul class="chunklist chunklist_chapter"><li><a href="security.filesystem.nullbytes.php">空字符（Null bytes）相关问题</a></li></ul>

   <p class="simpara">
    <abbr title="PHP: Hypertext Preprocessor">PHP</abbr> 受大多数服务器系统中文件和目录权限的内置安全机制的影响。
    这允许控制文件系统中哪些文件是可读的。应该小心对待任何全局可读的文件，
    要确保所有有权限访问该文件系统的用户都可以安全的读取文件。
   </p>
   <p class="simpara">
    <abbr title="PHP: Hypertext Preprocessor">PHP</abbr> 被设计为以用户级别访问文件系统，
    因此完全可以编写 <abbr title="PHP: Hypertext Preprocessor">PHP</abbr> 脚本来读取系统文件，例如 <var class="filename">/etc/passwd</var>、
    修改网络连接、发送大量打印任务等。这有一些明显的影响，因此需要确保读写的是合适的文件。
   </p>
   <p class="simpara">
    请看下面的脚本，用户表示想要删除自己主目录下的一个文件。
    假设 <abbr title="PHP: Hypertext Preprocessor">PHP</abbr> web 界面通常用于文件管理，
    因此 Apache 用户允许删除用户主目录中的文件。
   </p>
   <p class="para">
    <div class="example" id="example-1">
     <p><strong>示例 #1 不对变量检查会导致....</strong></p>
     <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #FF8000">// 从用户主目录移除一个文件<br /></span><span style="color: #0000BB">$username </span><span style="color: #007700">= </span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_submitted_name'</span><span style="color: #007700">];<br /></span><span style="color: #0000BB">$userfile </span><span style="color: #007700">= </span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_submitted_filename'</span><span style="color: #007700">];<br /></span><span style="color: #0000BB">$homedir  </span><span style="color: #007700">= </span><span style="color: #DD0000">"/home/</span><span style="color: #0000BB">$username</span><span style="color: #DD0000">"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">unlink</span><span style="color: #007700">(</span><span style="color: #DD0000">"</span><span style="color: #0000BB">$homedir</span><span style="color: #DD0000">/</span><span style="color: #0000BB">$userfile</span><span style="color: #DD0000">"</span><span style="color: #007700">);<br /><br />echo </span><span style="color: #DD0000">"The file has been deleted!"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
     </div>

    </div>
    由于 username 和 filename 由用户表单中提交，那就能提交属于其他人的 username
    和 filename，甚至可以删除不被允许的文件。这种情况下，
    应该使用一些其它形式的身份验证。不妨考虑一下，如果提交的变量是 <code class="literal">“../etc/”</code>
    和 <code class="literal">“passwd”</code> 会发生什么。上面代码将等同于：
    <div class="example" id="example-2">
     <p><strong>示例 #2 ... 文件系统攻击</strong></p>
     <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #FF8000">// 删除磁盘中任何 PHP 有访问权限的文件。如果 PHP 有 root 权限：<br /></span><span style="color: #0000BB">$username </span><span style="color: #007700">= </span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_submitted_name'</span><span style="color: #007700">]; </span><span style="color: #FF8000">// "../etc"<br /></span><span style="color: #0000BB">$userfile </span><span style="color: #007700">= </span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_submitted_filename'</span><span style="color: #007700">]; </span><span style="color: #FF8000">// "passwd"<br /></span><span style="color: #0000BB">$homedir  </span><span style="color: #007700">= </span><span style="color: #DD0000">"/home/</span><span style="color: #0000BB">$username</span><span style="color: #DD0000">"</span><span style="color: #007700">; </span><span style="color: #FF8000">// "/home/../etc"<br /><br /></span><span style="color: #0000BB">unlink</span><span style="color: #007700">(</span><span style="color: #DD0000">"</span><span style="color: #0000BB">$homedir</span><span style="color: #DD0000">/</span><span style="color: #0000BB">$userfile</span><span style="color: #DD0000">"</span><span style="color: #007700">); </span><span style="color: #FF8000">// "/home/../etc/passwd"<br /><br /></span><span style="color: #007700">echo </span><span style="color: #DD0000">"The file has been deleted!"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
     </div>

    </div>
    有两个重要措施来防止此类问题。
    <ul class="itemizedlist">
     <li class="listitem">
      <span class="simpara">
       <abbr title="PHP: Hypertext Preprocessor">PHP</abbr> web 用户二进制文件仅允许有限的权限。
      </span>
     </li>
     <li class="listitem">
      <span class="simpara">
       检查所有提交上来的变量。
      </span>
     </li>
    </ul>
    这是改进的脚本：
    <div class="example" id="example-3">
     <p><strong>示例 #3 更安全的文件名检查</strong></p>
     <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #FF8000">// 删除磁盘中 PHP 有权访问的文件。<br /></span><span style="color: #0000BB">$username </span><span style="color: #007700">= </span><span style="color: #0000BB">$_SERVER</span><span style="color: #007700">[</span><span style="color: #DD0000">'REMOTE_USER'</span><span style="color: #007700">]; </span><span style="color: #FF8000">// 使用认证机制<br /></span><span style="color: #0000BB">$userfile </span><span style="color: #007700">= </span><span style="color: #0000BB">basename</span><span style="color: #007700">(</span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_submitted_filename'</span><span style="color: #007700">]);<br /></span><span style="color: #0000BB">$homedir  </span><span style="color: #007700">= </span><span style="color: #DD0000">"/home/</span><span style="color: #0000BB">$username</span><span style="color: #DD0000">"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$filepath </span><span style="color: #007700">= </span><span style="color: #DD0000">"</span><span style="color: #0000BB">$homedir</span><span style="color: #DD0000">/</span><span style="color: #0000BB">$userfile</span><span style="color: #DD0000">"</span><span style="color: #007700">;<br /><br />if (</span><span style="color: #0000BB">file_exists</span><span style="color: #007700">(</span><span style="color: #0000BB">$filepath</span><span style="color: #007700">) &amp;&amp; </span><span style="color: #0000BB">unlink</span><span style="color: #007700">(</span><span style="color: #0000BB">$filepath</span><span style="color: #007700">)) {<br />    </span><span style="color: #0000BB">$logstring </span><span style="color: #007700">= </span><span style="color: #DD0000">"Deleted </span><span style="color: #0000BB">$filepath</span><span style="color: #DD0000">\n"</span><span style="color: #007700">;<br />} else {<br />    </span><span style="color: #0000BB">$logstring </span><span style="color: #007700">= </span><span style="color: #DD0000">"Failed to delete </span><span style="color: #0000BB">$filepath</span><span style="color: #DD0000">\n"</span><span style="color: #007700">;<br />}<br /><br /></span><span style="color: #0000BB">$fp </span><span style="color: #007700">= </span><span style="color: #0000BB">fopen</span><span style="color: #007700">(</span><span style="color: #DD0000">"/home/logging/filedelete.log"</span><span style="color: #007700">, </span><span style="color: #DD0000">"a"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">fwrite</span><span style="color: #007700">(</span><span style="color: #0000BB">$fp</span><span style="color: #007700">, </span><span style="color: #0000BB">$logstring</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">fclose</span><span style="color: #007700">(</span><span style="color: #0000BB">$fp</span><span style="color: #007700">);<br /><br />echo </span><span style="color: #0000BB">htmlentities</span><span style="color: #007700">(</span><span style="color: #0000BB">$logstring</span><span style="color: #007700">, </span><span style="color: #0000BB">ENT_QUOTES</span><span style="color: #007700">);<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
     </div>

    </div>
    然而，这样做也是有缺陷的。如果认证系统允许用户创建自己的登录用户名，
    而用户选择 <code class="literal">“../etc/”</code> 作为登录名，系统将再次暴露。
    出于这个原因，需要编写自定义检查：
    <div class="example" id="example-4">
     <p><strong>示例 #4 更安全的文件名检查</strong></p>
     <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br />$username     </span><span style="color: #007700">= </span><span style="color: #0000BB">$_SERVER</span><span style="color: #007700">[</span><span style="color: #DD0000">'REMOTE_USER'</span><span style="color: #007700">]; </span><span style="color: #FF8000">// 使用认证机制<br /></span><span style="color: #0000BB">$userfile     </span><span style="color: #007700">= </span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_submitted_filename'</span><span style="color: #007700">];<br /></span><span style="color: #0000BB">$homedir      </span><span style="color: #007700">= </span><span style="color: #DD0000">"/home/</span><span style="color: #0000BB">$username</span><span style="color: #DD0000">"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$filepath     </span><span style="color: #007700">= </span><span style="color: #DD0000">"</span><span style="color: #0000BB">$homedir</span><span style="color: #DD0000">/</span><span style="color: #0000BB">$userfile</span><span style="color: #DD0000">"</span><span style="color: #007700">;<br /><br />if (!</span><span style="color: #0000BB">ctype_alnum</span><span style="color: #007700">(</span><span style="color: #0000BB">$username</span><span style="color: #007700">) || !</span><span style="color: #0000BB">preg_match</span><span style="color: #007700">(</span><span style="color: #DD0000">'/^(?:[a-z0-9_-]|\.(?!\.))+$/iD'</span><span style="color: #007700">, </span><span style="color: #0000BB">$userfile</span><span style="color: #007700">)) {<br />    die(</span><span style="color: #DD0000">"Bad username/filename"</span><span style="color: #007700">);<br />}<br /><br /></span><span style="color: #FF8000">//等等...<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
     </div>

    </div>
   </p>
   <p class="para">
    根据操作系统的不同，需要关心各种各样的文件，比如设备条目（<var class="filename">/dev/</var>
    或 <var class="filename">COM1</var>）、配置文件（<var class="filename">/etc/</var> 文件和 <code class="literal">.ini</code> 文件）、
    众所周知的文件存储区域（<var class="filename">/home/</var>，<var class="filename">My Documents</var>）等等。出于这个原因，
    创建一个禁止所有权限而只开放明确允许的策略通常更容易些。
   </p>
   

  </div>
<?php manual_footer($setup); ?>