安装为 CGI 程序

可能受到的攻击

如果您不希望将 PHP 以模块的方式集成到服务器软件(例如 Apache)中,或者希望将 PHP 用于不同的 CGI 外壳来建立安全的 chroot 和 setuid 脚本环境,将 PHP 用作 CGI 程序是安装时的一种选择。这样的安装通常都需要将可执行的 PHP 程序放置到 cgi-bin 目录。CERT 顾问 CA-96.11 的建议反对将任何解释器放入 cgi-bin 目录。尽管 PHP 的可执行程序能够被用作独立的解释器,PHP 的安全机制也能防止由这样的安装所导致的可能的攻击:

  • 访问系统文件:http://my.host/cgi-bin/php?/etc/passwd

    在 URL 中问号(?)后面的查询信息将被 CGI 接口作为命令行参数传给解释器。通常情况下,解释器会打开并运行由命令行第一个参数所指明的文件。

    当 PHP 以 CGI 程序的方式被激活时,它将拒绝解释该命令行参数。

  • 访问服务器上的任何 WEB 文档:http://my.host/cgi-bin/php/secret/doc.html

    URL 中 PHP 程序名称后面的路径信息 /secret/doc.html 习惯上是用来指明由 CGI 程序打开并解释的文件名。通常 WEB 服务器会通过一些的配置选项(Apache: Action)将对类似于 http://my.host/secret/script.php 文档的请求重定向到 PHP 解释器。WEB 服务器先检查目录 /secret 的访问权限,然后再建立请求的重定向 http://my.host/cgi-bin/php/secret/script.php。但不幸的是,如果请求直接以这种形式给出,那么 WEB 服务器就不会执行对文件 /secret/script.php 的访问权限检查,而仅仅只检查文件 /cgi-bin/php 的访问权限。这样一来,任何可以访问 /cgi-bin/php 的用户都可以访问任何 WEB 服务器上收到保护的文档。

    在 PHP 中,编译时设置选项 --enable-force-cgi-redirect 以及运行时设置选项 doc_rootuser_dir 能够用来防止这种攻击,如果服务端的目录树存在有访问限制的目录。以下将详细解释各种不同的组合。

方法一:仅就公众文件提供服务

如果您的服务器上没有任何不被密码或者基于 IP 访问限制控制的内容,则这些选项配置对您并没有作用。如果您的 WEB 服务器不允许您进行重定向,或者服务器无法和 PHP 程序通信以识别安全的重定向请求,您可以在运行配置脚本的时候使用 --enable-force-cgi-redirect 参数。您还需要确认您的 PHP 脚本不仅仅只依赖某一种方法来调用脚本,不管是直接调用 http://my.host/cgi-bin/php/dir/script.php,还是重定向到 http://my.host/dir/script.php

在 Apache 中重定向可以使用选项 AddHandler 和 Action 来配置(请参考下文)。

方法二:使用 --enable-force-cgi-redirect 参数

该编译时参数防止任何人通过直接访问类似于 http://my.host/cgi-bin/php/secretdir/script.php 的 URL 来调用 PHP。也就是说,只有当脚本基于合法的重定向规则被访问时,PHP 才对它们进行解析。

通常 Apache 的重定向设置是通过以下选项完成的:

Action php-script /cgi-bin/php
AddHandler php-script .php

该选项仅仅在 Apache WEB 服务器下做过测试,并且依赖 Apache 为被重定向的请求设置非标准的 CGI 环境变量 REDIRECT_STATUS。如果您的 WEB 服务器不支持任何方式以获悉请求是直接的还是被重定向的,您将无法使用该选项。您必须使用本文提及的其它运行 CGI 版本的方法。

方法三:设置 doc_root 或者 user_dir

在 WEB 服务器的文档目录中放置诸如脚本和可执行程序等主动的内容,常常被认为是一种不安全的做法。如果由于一些设置的错误,这些脚本没有被执行而像通常的 HTML 文档一样被显示,这将可能导致知识产权或者诸如密码等安全信息的泄漏。因此很多系统管理员宁愿为脚本建立另外一个只能被 PHP CGI 访问的目录结构,使得它们能够被正确解析,又不会出现上述的问题。

同样,如果如前面章节描述的确保请求不被重定向的方法无效,那么我们就有必要建立一个区别于 WEB 文档根目录脚本的 doc_root 目录。

您可以通过配置文件中的选项 doc_root 或者环境变量 PHP_DOCUMENT_ROOT 来设置 PHP 脚本文档的根目录。如果该选项被设置,那么 PHP 的 CGI 版本将总是只通过 doc_root 和请求的路径信息来打开文档,这样您就可以确保脚本不会在该目录以外执行(除了以下将提及的 user_dir 目录)。

另外一个这里可能用得到的选项是 user_dir。如果 user_dir 没有被设置,那么唯一控制被打开的文件名的选项就是 doc_root。打开一个类似于 http://my.host/~user/doc.php 将不会导致打开一个用户主目录中的文件,而是 doc_root 下一个名为 ~user/doc.php 的文件(即一个以波浪号[~]开头的目录名)。

如果 user_dir 被设置为,例如, public_php,那么类似于 http://my.host/~user/doc.php 地请求将打开一个该用户主目录下名为 public_php 的目录下的名为 doc.php 的文件。如果用户的主目录是 /home/user,那么被执行的文件是 /home/user/public_php/doc.php

user_dir 扩展并不依赖于 doc_root 选项的设定,因此您可以独立控制文档根目录和用户访问目录。

方法四:在 WEB 目录树以外进行 PHP 解析

一种安全性非常高的方法是把 PHP 解析程序放置到 WEB 文件目录树以外的某个地方,例如,放置到 /usr/local/bin。这种做法唯一的弊病就是您现在需要在所有含有 PHP 标记符文件的第一行添加类似于以下的内容:

#!/usr/local/bin/php

您需要使得这些文件成为可执行文件。也就是说,像对待任何其它 CGI 脚本一样来对待它们。这些 CGI 脚本可能是用 Perl、sh 或者任何其它使用的 #! shell-escape 机制来启动它们自身的脚本语言来编写的。

要使这个方法中 PHP 能够正确处理 PATH_INFOPATH_TRANSLATED 信息,在编译 PHP 解析器时必须使用配置参数 --enable-discard-path