在PHP5.3以下版本都会有安全模式safe_mode这个功能,只是默认状态都是关闭safe_mode安全模式的,下面我来介绍一下具体关于safe_mode开启与对系统的影响。
开启PHP安全模式(请注意,PHP5.3将不再有安全模式)
打开或者关闭php的安全模式是利用php.ini中的safe_mode选项:
代码如下 | 复制代码 |
safe_mode=On(使用安全模式) safe_mode=Off(关闭安全模式) 在apache的httpd.conf中VirtualHost的相应设置方法 php_admin_flag safe_mode On(使用安全模式) php_admin_flag safe_mode Off(关闭安全模式) 或者: php_admin_value safe_mode1(使用安全模式) php_admin_value safe_mode0(关闭安全模式)
|
名称 | 默认值 | 可修改范围 | 更新记录 |
---|---|---|---|
safe_mode | "0" | PHP_INI_SYSTEM | |
safe_mode_gid | "0" | PHP_INI_SYSTEM | 自 PHP 4.1.0 起可用 |
safe_mode_include_dir | NULL | PHP_INI_SYSTEM | 自 PHP 4.1.0 起可用 |
safe_mode_exec_dir | "" | PHP_INI_SYSTEM | |
safe_mode_allowed_env_vars | "PHP_" | PHP_INI_SYSTEM | |
safe_mode_protected_env_vars | "LD_LIBRARY_PATH" | PHP_INI_SYSTEM | |
open_basedir | NULL | PHP_INI_SYSTEM | |
disable_functions | "" | 仅 php.ini | 自 PHP 4.0.1 起可用 |
disable_classes | "" | 仅 php.ini | 自 PHP 4.3.2 起可用 |
当安全模式打开的时候,以下函数列表的功能将会受到限制:
函数名 | 限制 |
---|---|
dbmopen() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
dbase_open() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
filepro() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
filepro_rowcount() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
filepro_retrieve() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
ifx_* | sql_safe_mode 限制, (!= safe mode) |
ingres_* | sql_safe_mode 限制, (!= safe mode) |
mysql_* | sql_safe_mode 限制, (!= safe mode) |
pg_loimport() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
posix_mkfifo() | 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
putenv() | 遵循 ini 设置的 safe_mode_protected_env_vars 和 safe_mode_allowed_env_vars 选项。请参考 putenv() 函数的有关文档。 |
move_uploaded_file() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
chdir() | 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
dl() | 本函数在安全模式下被禁用。 |
backtick operator | 本函数在安全模式下被禁用。 |
shell_exec()(在功能上和 backticks 函数相同) | 本函数在安全模式下被禁用。 |
exec() | 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。 |
system() | 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。 |
passthru() | 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。 |
popen() | 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。 |
fopen() | 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
mkdir() | 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
rmdir() | 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
rename() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
unlink() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
copy() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (on <font face="NSimsun">source</font> and <font face="NSimsun">target</font> ) |
chgrp() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
chown() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 |
chmod() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 另外,不能设置 SUID、SGID 和 sticky bits |
touch() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 |
symlink() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意:仅测试 target) |
link() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意:仅测试 target) |
apache_request_headers() | 在安全模式下,以“authorization”(区分大小写)开头的标头将不会被返回。 |
header() | 在安全模式下,如果设置了 WWW-Authenticate,当前脚本的 uid 将被添加到该标头的 realm 部分。 |
PHP_AUTH 变量 | 在安全模式下,变量 <font face="NSimsun">PHP_AUTH_USER</font> 、<font face="NSimsun">PHP_AUTH_PW</font> 和 <font face="NSimsun">PHP_AUTH_TYPE</font> 在 <font face="NSimsun">$_SERVER</font> 中不可用。但无论如何,您仍然可以使用 <font face="NSimsun">REMOTE_USER</font> 来获取用户名称(USER)。(注意:仅 PHP 4.3.0 以后有效) |
highlight_file(), show_source() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意,仅在 4.2.1 版本后有效) |
parse_ini_file() | 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意,仅在 4.2.1 版本后有效) |
set_time_limit() | 在安全模式下不起作用。 |
max_execution_time | 在安全模式下不起作用。 |
mail() | 在安全模式下,第五个参数被屏蔽。(注意,仅自 PHP 4.2.3 起受影响) |
同样的,一些php扩展中的函数也将会受到影响。(加载模块:在安全模式下dl函数将被禁止,如果要加载扩展的话,只能修改php.ini中的扩展选项,在php启动的时候加载)
在php安全模式打开的时候,需要执行系统程序的时候,必须是在safe_mode_exec_dir选项指定目录的程序,否则执行将失败。即使允许执行,那么也会自动的传递给escapeshellcmd函数进行过滤。
以下执行命令的函数列表将会受到影响:
exec,shell_exec,passthru,system,popen
另外,背部标记操作符(`)也将被关闭。
当运行在安全模式下,虽然不会引起错误,但是putenv函数将无效。同样的,其他一些尝试改变php环境变量的函数set_time_limit, set_include_path也将被忽略。
安全模式后的影响:
当函数在访问文件系统的时候将进行文件所有者的检查。缺省情况下,会检查该文件所有者的用户id,当你能够修改文件所有者的组id(gid)为safe_mode_gid选项所指定的。
如果你有一个共享库文件在你的系统上,当你碰到需要include或require的时候,那么你可以使用safe_mode_include_dir选项来设置你的路径,保证你的代码正常工作。(包含路径:如果你想要使用safe_mode_include_dir选项包含更多的包含路径,那么你可以象include_path选项一样,在unix/linux系统下使用冒号进行分割,在windows下使用分号进行分割)
比如你想要在安全模式下包含/usr/local/include/php下的文件,那么你可以设置选项为:
safe_mode_include_dir=/usr/local/include/php
如果你的包含的文件是需要执行的,那么你可以设置safe_mode_exec_dir选项。
比如你需要/usr/local/php-bin路径下的文件是可以执行的,那么可以设置选项为:
safe_mode_exec_dir=/usr/local/php-bin
(可执行:如果你执行的程序在/usr/bin目录下,那么你可以把这些的二进制文件,连接到你指定选项下能够执行的路径)
如果你想设置某些环境变量,那么可以使用safe_mode_allowed_env_vars选项。这个选项的值是一个环境变量的前缀,缺省是允许php_开头的环境变量,如果你想要改变,可以设置该选项的值,多个环境变量前缀之间使用逗号进行分割。
比如下面允许时区的环境变量tz,那么修改该选项的值为:
safe_mode_allowed_env_vars=php_,tz
除了安全模式以外,php还提供了许多其他许多特征来保证php的安全。
1、[隐藏php的版本号]
你能够在php.ini里使用expose_php选项来防止web服务器泄露php的报告信息。如下:
expose_php=on
利用整个设置,你能够阻碍一些来自自动脚本针对web服务器的攻击。通常情况下,http的头信息里面包含了如下信息:
server:apache/1.3.33(unix)php/5.2.4mod_ssl/2.8.16openssl/0.9.7c
在expose_php选项打开以后,php的版本信息将不包含在上面的头信息里。
当然,用户访问网站的时候同样能够看到.php的文件扩展名。如果你想整个的使用不同的文件扩展名,你需要在httpd.conf中找到如下这行:
addtype application/x-httpd.php
你就可以修改.php为任何你喜欢的文件扩展名。你能够指定任意多个的文件扩展名,中间使用空格进行分割。如果你想在服务器端使用php来解析.html和.htm文件的时候,那么你设置选项如下:
addtype application/x-httpd.html.htm
(解析html:配置你的web服务器使用php去解析所有的html文件,但是如果非服务器端代码也需要php去解析,会影响服务器的性能。静态页面你可以使用不同的扩展名,这样能够消除对php脚本引擎的依赖,增强性能。)
2、[文件系统安全]
安全模式限制了脚本所有者只能访问属于自己的文件,但是你可以使用open_basedir选现来指定一个你必须访问的目录。如果你指定了一个目录,php将拒绝访问除了该目录和该目录子目录的其他目录。open_basedir选项能够工作在安全模式之外。
限制文件系统只能访问/tmp目录,那么设置选项为:
open_basedir=/tmp
3、[函数访问控制]
你能够在disable_functions选项中使用逗号分割来设定函数名,那么这些函数将在php脚本中被关闭。这个设置能够工作在安全模式之外。
disable_functions=dl
当然,同样的你能够使用disable_classes选项来关闭对一些类的访问。
4、[数据库安全]
假设你的php脚本中包含一个基于表单值来执行的mysql查询:
$sql=”update mytable set col1=”.$_post["value"].”where col2=’somevalue’”;
$res=mysql_query($sql,$db);
你希望$_post["value"]包含一个整数值来更新你的列col1。可是,一个恶意用户能够输入一个分号在表单字段里,接着,是一段他/她想被任意执行的sql语句。
举例,假设下面是$_post["value"]提交的值:
0;insert into admin_users(username,password) values (‘me’,'mypassword’);
那么当这个查询发送给mysql查询的时候,那么就变成了下面这条sql:
update mytable set col1=0;
insert into admin_users(username,password) values (‘me’,'mypassword’);
where col2=’somevalue’;
这明显是一个有害的查询!首先这个查询会在mytable表里更新col1。这个并没有什么麻烦的,但是第二个表达式,它将执行insert表达式来插入一个能登陆的新管理员。第三个表达式就废弃了,但同时sql解析器将抛出一个错误,这个有害的查询才完成。这个攻击就是大家常说的sql injection(注:sql注入)。
当然,sql injection存在一个问题,对方必须了解你的数据库结构。在这个例子中,攻击者是知道你有一个表admin_users,并且知道包含username和password字段,同时,存储的密码是没有加密的。
除了你自己,一般的网站访问者是不知道这些关于数据库的信息。可是,如果你使用了一个开发源代码的在线电子商务程序,或者使用一个自由的讨论版程序,这些数据表的定义都是已知的,或者有一些用户能够访问到你的数据库。
此外,你的脚本输出会提示一个查询错误,这些信息里包含了很多关于数据库结构的重要信息。在一个正常工作的网站上,你应该考虑设置display_errors选项为off,并且使用log_errors来代替display_errors,把警告和错误信息插入到文件中。
(数据库权限:它是一个非常重要的东西,你只有正确的权限,才能通过脚本正确的连接数据库。你应该不要在脚本中使用管理员去连接数据库。如果你这么做,那么一个攻击者将可能获取全部的数据库权限,并且包括其他相同服务器的权限。攻击者将可能运行grant或create user命令来获取更多的访问权限。)
如果你要防止sql injection攻击,你必须保证用户表单里提交的内容不是一个能够执行的sql表达式。
前一个例子中,我们使用一个整型值来进行更新。如果在单引号后面跟上一个字符串,这个攻击者在分号之前必须提交一个闭合的引用在整个sql表达式中。可是,当magic_quotes_gpc选项是开启的时候,在web表单中提交的引号将自动被转义。
为了防止被恶意的攻击者进行sql injection攻击,你应该总是确认提交的数据是合法的。如果你需要的是一个整数值,那么你可以使用is_numeric函数来测试这个表达值,或者使用settype函数来转换为一个数字,清除任何一个傻傻的sql语句。
如果你开发的程序需要几个提交的值在一个sql表达式里,你能够使用sprintf函数来构建一个sql字符串,使用格式化字符来指示数据类型的每个值。看下面的例子:
$sql=sprintf(“update mytable set col1=%d where col2=’%s’”, $_post["number"], mysql_escape_string($_post["string"]));
在上一个例子中,整个mysql的数据已经被使用,所以这个字符串已经通过mysql_escape_string函数进行过滤。对于其他数据库,你可以使用addslashes函数进行转义,或者使用其他方法