如果不想把 PHP 嵌入到服务器端软件(如 Apache)作为一个模块安装的话,可以选择以 CGI 的模式安装。在PHP以CGI模式安装时,需要注意一些安全问题,否则可能会遭受到攻击。本文将介绍可能会受到攻击的几种情形,并提供相应的解决方案。
一、可能受到的攻击
如果不想将 PHP 嵌入到服务器端软件(如 Apache)中作为一个模块安装,可以选择以 CGI 模式安装。或者可以使用不同的 CGI 封装来为代码创建安全的 chroot 和 setuid 环境。这种安装方式通常会将 PHP 的可执行文件安装到 web 服务器的 cgi-bin 目录中。
建议不要将任何解释器放在 cgi-bin 目录中。尽管 PHP 可以作为独立的解释器,但它的设计使其能够防止以下类型的攻击:
1、访问系统文件:http://my.host/cgi-bin/php?/etc/passwd URL 请求中的问号(?)后面的信息会作为命名行参数传递给 CGI 接口。其他解释器会在命令行中打开并执行第一个参数指定的文件。但是,以 CGI 模式安装的 PHP 解释器在被调用时会拒绝解释这些参数。
2、访问服务器上的任意目录:http://my.host/cgi-bin/php/secret/doc.html 类似于上述情况,PHP 解释器所在目录后面的 URL 信息 /secret/doc.html 会传递给 CGI 程序并进行解释。通常一些 web 服务器会将其重定向到页面,如 http://my.host/secret/script.php。如果是这样,某些服务器会先检查用户访问 /secret 目录的权限,然后才会创建 http://my.host/cgi-bin/php/secret/script.php 上的页面重定向。
很多服务器并没有检查用户访问 /secret/script.php 的权限,只检查了 /cgi-bin/php 的权限,这样任何能访问 /cgi-bin/php 的用户就可以访问 web 目录下的任意文件了。在 PHP 中,运行时配置指令 cgi.force_redirect、doc_root 和 user_dir 都可以限制服务器上的文件和目录的访问,以防止这类攻击。下面将对各个选项的设置进行详细讲解。
二、可能遇到的情形
1、只运行公开的文件
如果 web 服务器中所有内容都受到密码或 IP 地址的访问限制,就不需要设置这些选项。如果 web 服务器不支持重定向,或者 web 服务器不能和 PHP 通信而使访问请求变得更为安全,可以开启 cgi.force_redirect ini 指令。
除此之外,还要确认 PHP 程序不依赖其它方式调用,比如通过直接的 http://my.host/cgi-bin/php/dir/script.php 访问或通过重定向访问 http://my.host/dir/script.php。
2、使用cgi.force_redirect
配置指令 cgi.force_redirect 可以防止任何人通过如 http://my.host/cgi-bin/php/secretdir/script.php 这样的 URL 直接调用 PHP。PHP 在此模式下只会解析已经通过了 web 服务器的重定向规则的 URL。
通常,在 Apache 中可以通过以下指令完成重定向设置:
Action php-script /cgi-bin/php AddHandler php-script .php
此选项仅在 Apache 下进行过测试,并且依赖于 Apache 在重定向操作中所设置的非标准 CGI 环境变量 REDIRECT_STATUS。
3、设置doc_root或user_dir
在 web 服务器的主文档目录中包含动态内容如脚本和可执行程序有时被认为是一种不安全的实践。如果因为配置上的错误而未能执行脚本而作为普通 HTML 文档显示,那就可能导致知识产权或密码资料的泄露。因此,很多系统管理员都会专门设置一个只能通过 PHP CGI 来访问的目录,这样该目录中的内容只会被解析而不会原样显示出来。
对于前面所说无法判断是否重定向的情况,很有必要在主文档目录之外建立一个专用于脚本的 doc_root 目录。可以通过配置文件内的 doc_root 指令或设置环境变量 PHP_DOCUMENT_ROOT 来定义 PHP 脚本主目录。如果设置了该项,那么 PHP 的 CGI 版本就只会解释 doc_root 目录下的文件,并确保目录外的脚本不会被 PHP 解释器执行(下面所说的 user_dir 除外)。
另一个可用的选项就是 user_dir。当 user_dir 没有设置的时候,doc_root 就是唯一能控制在哪里打开文件的选项。访问如 http://my.host/~user/doc.php 这个 URL 时,并不会打开用户主目录下文件,而只会执行 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 无关,所以可以分别控制 PHP 脚本的主目录和用户目录。
4、PHP解释器放在web目录以外
一个非常安全的做法就是把 PHP 解释器放在 web 目录外的地方,比如说 /usr/local/bin。这样做唯一不便的地方就是必须在每一个包含 PHP 代码的文件的第一行加入如下语句:
#!/usr/local/bin/php
还要将这些文件的属性改成可执行。也就是说,要像处理用 Perl 或 sh 或其它任何脚本语言写的 CGI 脚本一样,使用以 #! 开头的 shell-escape 机制来启动它们。
要使 PHP 能使用此设置正确处理 PATH_INFO 和 PATH_TRANSLATED 信息,需要开启 cgi.discard_path ini 指令。