Apache与PHP安全设置
2012-06-07 15:18:12 阿炯

对于提供公共网络服务的web服务器,从安全性的角度考虑,建议apache和php都使用最小权限的设置。针对特定用户所提出的涉及安全性能的特殊要求,可以在不改变全局性的共用设置的情况下通过利用Apache Virtualhost的PHP扩展功能来实现。只需在相应用户的Virtualhost的设置段落中插入php_value,php_admin_value或php_admin_flag指令,就可以使该用户具有与全局设置不同的权限和行为。

针对Apache的特定虚拟用户进行单独配置的相关语法如下: 
php_admin_value name 1|0|string (value控制具体的参数) 
php_admin_flag name on|off (flag控制on或off,适用于Apache2.20版本) 

请注意: 上述针对虚拟用户的设置命令,可以直接设置在单元里面,或者设置在相应用户的单元里面. 

例如,要将ernest这个用户的register_globals功能打开,并且将upload_max_filesize调高到5M,同时关闭safe_mode,但又不影响其他用户,就可以在该用户的VirtualHost里面加如下几行: 
<VirtualHost ip_addr> 
php_value upload_max_filesize 5M 
php_value register_globals 1 
php_value safe_mode 0 
php_flag safe_mode Off 
php_admin_value safe_mode 0 #(for Apache2.20) 
php_admin_flag safe_mode Off #(for Apache2.20) 
</VirtualHost> 

在php.ini配置文件中的大部分功能,均可以用这种方式来调整,调整后应重新启动apache,然后就可以在phpinfo中看到中间栏的Local Value同右边栏位的Master Value是不同值. 

请注意: 有些参数值的设定方法跟它们在php.ini配置文件中的设置方法可能不一样,例如上面的”register_globals 1″, 原来在php.ini中应是”register_globals On”. 

(1) safe_mode: 以安全模式运行PHP; 
——————————————————————————– 
在php.ini文件中使用如下选项(这是影响全局的设置): 
safe_mode = On (使用安全模式) 
safe_mode = Off (关闭安全模式) 
PHP的安全模式是为了试图解决共享伺服器(shared-server)的安全问题而专门设立的. 然而从结构上看, 试图在PHP层面上解决这个问题其实是不合理的, 只是考虑到修改WEB伺服器层和操作系统层都显得非常的不现实, 因此许多使用者,特别是提供公共网络服务的ISP供应商,大多都在其服务器中要求以安全模式来运行PHP,用以防止合法用户的跨站读取或越权操作等危险行为, 以及将非授权用户的恶意行为所造成的影响降到最低范围. 

在Apache的httpd.conf中VirtualHost的相应设置方法(这是针对特定用户的设置): 
php_admin_flag safe_mode On (使用安全模式) 
php_admin_flag safe_mode Off (关闭安全模式) 
或者: 
php_admin_value safe_mode 1 (使用安全模式) 
php_admin_value safe_mode 0 (关闭安全模式) 

严重警告: 如果在全局性设置中已经启用了safe_mode的功能,但又在特别的用户虚拟空间中关闭该用户的safe_mode的功能,这就等于给予了该用户特殊的权限,允许他不须受safe_mode的限制而自由地使用系统的服务,也就是说所有原来被全局性的safe_mode功能所禁止的行为,例如跨站读取或越权操作等都可以被该用户执行, 这就好像在原本安全设防的金库中打开了一个可供该用户自由进出的洞,因此任何使用该用户空间的应用都不再被全局性的safe_mode保护所限制,当然这就意味著整个系统的安全性都可能会受到该用户空间的影响,包括可能因该空间应用的漏洞而导致整个系统被入侵等等. 所以,这样的针对特定用户的特殊设置,应当作为一种特许授权的方式来考虑, 并有必要建立有效的监控机制以防止该用户滥用系统资源,否则, 一旦该用户变得不再可信任或他的网站程序存在漏洞, 那么您的整体系统所受的影响就会同完全没有启用safe_mode一模一样. 

(2) safe_mode_include_dir: 无需UID/GID检查的目录 
——————————————————————————– 
当您按照前面(1)所述之设置启用PHP的安全模式之后,PHP的脚本在运行时就会对所有被操作的目录以及文件进行针对UID/GID的匹配性检查: 即检查被操作目录或文件的UID或GID,是否同当前PHP脚本文件的UID或GID一样.然而, 如果您的系统允许用户的PHP脚本访问公共路径的话(例如很多较旧的Forum或Gallery程序都会直接引用系统文件来扩展当时PHP还未能支持的功能), 那么这种设置就会造成麻烦. 

而使用safe_mode_include_dir设置可以指定某些目录, 当PHP脚本操作这些目录及其子目录时(该目录必须在include_path中或者用完整路径来包含), 则允许越过UID/GID检查,即不对该目录进行UID/GID匹配性检查. 

从PHP4.2.0开始, 这个指令已经可以接受同include_path指令类似的风格, 即用分号隔开的多个路径, 而以前则只能指定单一个目录. 同open_basedir一样, 它所指定的路径实际上也是一个字符串的前缀限制,而非针对该目录名称空间的操作. 

例如如果指定: “safe_mode_include_dir = /dir/incl”, 那么所有的PHP脚本都将允许任意访问 “/dir/include”和“/dir/incls” 路径(如果它们存在的话). 因此, 如果您希望将访问控制在一个指定的目录里面, 就必须在上述设置的指定路径的结尾加上一个斜线, 例如: 
“safe_mode_include_dir = /dir/incl/” 

请注意: VirtualHost会自动继承php.ini中的safe_mode_include_dir设置. 

(3) open_basedir: 将用户可操作的文件限制在某目录下; 
——————————————————————————– 
如下是php.ini中的原文说明以及默认配置: 
; open_basedir, if set, limits all file operations to the defined directory 
; and below. This directive makes most sense if used in a per-directory or 
; per-virtualhost web server configuration file. This directive is 
; *NOT* affected by whether Safe Mode is turned On or Off. 
open_basedir = . 

open_basedir可将用户访问文件的活动范围限制在指定的区域,通常是其家目录的路径,也可用符号”.”来代表当前目录。注意用open_basedir指定的限制实际上是前缀,而不是目录名。 
举例来说: 若”open_basedir = /dir/user”, 那么目录 “/dir/user” 和 “/dir/user1″都是可以访问的。所以如果要将访问限制在仅为指定的目录,请用斜线结束路径名。例如设置成: 
“open_basedir = /dir/user/” 

open_basedir也可以同时设置多个目录, 在Windows中用分号分隔目录,在任何其它系统中用冒号分隔目录。当其作用于Apache模块时,父目录中的open_basedir路径自动被继承。 

有三种方法可以在Apache中为指定的用户做独立的设置: 
(a) 在Apache的httpd.conf中Directory的相应设置方法: 
<Directory /usr/local/apache/htdocs> 
php_admin_value open_basedir /usr/local/apache/htdocs/ 
#设置多个目录可以参考如下: 
php_admin_value open_basedir /usr/local/apache/htdocs/:/tmp/ 
</Directory> 

(b) 在Apache的httpd.conf中VirtualHost的相应设置方法: 
php_admin_value open_basedir /usr/local/apache/htdocs/ 
#设置多个目录可以参考如下: 
php_admin_value open_basedir /var/www/html/:/var/tmp/ 

(c) 因为VirtualHost中设置了open_basedir之后, 这个虚拟用户就不会再自动继承php.ini中的open_basedir设置值了,这就难以达到灵活的配置措施, 所以建议您不要在VirtualHost 中设置此项限制. 例如,可以在php.ini中设置open_basedir = .:/tmp/, 这个设置表示允许访问当前目录(即PHP脚本文件所在之目录)和/tmp/目录. 

请注意: 若在php.ini所设置的上传文件临时目录为/tmp/, 那么设置open_basedir时就必须包含/tmp/,否则会导致上传失败. 新版php则会提示”open_basedir restriction in effect” 警告信息, 但move_uploaded_file()函数仍然可以成功取出/tmp/目录下的上传文件,不知道这是漏洞还是新功能. 

(4) disable_functions: 单独地屏蔽某些函式(常用于禁止普通用户执行系统函数); 
——————————————————————————– 
这个指令允许你基于安全原因直接禁止某些确定的函式(通常是攸关系统安全的函数),例如: 
disable_functions = shell_exec,system,exec,passthru,show_source,get_cfg_var 
 
disable_functions接受逗号分隔的函式名列表作为参数, 它不受安全模式的影响,而且只能设置在php.ini中用作全局性配置, 不能将其设置在httpd.conf中针对单独用户来进行设置. 

从php-4.0.1开始在php.ini里引入了此项功能, 这个功能非常有用, 可以用它禁止用户使用一些具有潜在的危险性的函数, 例如: passthru,exec,system,popen 等等. 当您在php.ini 
中加上 disable_functions = passthru,exec,system,popen 配置后, PHP在执行这些函数时就只会显示错误提示: Warning: system() has been disabled for security reasons 

下面举个例子来看看这个安全性设置的重要程度: 
我们知道PHP脚本可以采用很多perl的特性,例如通过一种叫shell_exec的方法来执行系统的命令, 只需在一对反引号(“)中包含调用系统命令的script代码, 就能执行相应的系统命令. 
例如: 
<? 
$output = `ls /etc -al`; 
echo $output; 
?> 
显然,如果您的系统不加限制的话,那么任何用户都可以通过诸如 `cat /etc/passwd` 这样的命令攫取系统信息或进行破坏行为. 这对于提供公共服务的ISP供应商来说, 等于是打开自家金库大门让所有客户自由出入, 而且一旦某些客户的PHP程序存在安全漏洞(就目前PHP程序员的平均水准来看,存在严重的漏洞基本上是不可避免的)的话, 那么只需最低级的黑客都可以很简单地完全操控您的主机了. 因此一定要防止Linux用户在PHP程序中通过“来执行script脚本,这可以通过在PHP.INI中设置: disable_functions = shell_exec,system,exec 来禁止PHP调用相关系统函数. 

典型的安全性配置,请参考如下设置: 
disable_functions = shell_exec,system,exec,passthru,show_source,get_cfg_var 

若允许用户调试程序,则可配置如下: 
disable_functions = shell_exec,system,exec,passthru 

PHP中一些常用但有安全风险的函数: 
unlink,mkdir,touch,fgets,popen,proc_open,link,symlink,phpinfo 

建议: 应兼顾到商业服务的完整性和安全性,请酌情考虑是否禁止使用它们. 

请注意: disable_functions选项不能在php.ini文件外部使用,也就是说您无法在httpd.conf文件中按不同虚拟主机或不同目录的方式来屏蔽或者开启函式。 

(5) register_globals: 禁止注册全局变量; 
——————————————————————————– 
register_globals = On (自动注册为全局变量) 
register_globals = Off (不可注册为全局变量) 
一般情况下,用户都是在HTML网页里通过HTTP协议,来提交GET,POST和COOKIE数据(简称为GPC)的. 而PHP程序如何获得用户提交的这些变量数据,则还需依赖于php.ini配置中一个有争议的设置,即是register_globals参数来决定. 

顾名思义,register_globals的意思就是注册为全局变量, 所以当设置为On的时候, 通过页面传递过来的值就会被直接的注册为全局变量,可以很方便地提供给PHP程序直接使用;而当设置为Off的时候,PHP程序要使用网页传递过来的变数,就需要到特定的数组里才能得到它. 

在PHP4.3.0以后,register_globals默认情况下被设置为Off; 但是几年前,register_globals的默认值还是打开的,所以现在依然还存在很多需要启用它的程序代码. 

请注意: 当设置为 register_globals = Off 之后,不仅会影响到PHP如何获取从的URL所传递过来的数据,也会影响到PHP获取session和cookie的方式. 当关闭register_globals时,PHP程序就必须使用相应的数组来获取session和cookie(例如$_SESSION[] 和$_COOKIE). 同时对于session的处理也有一些改变,比如使用session_register()就显得再也没有必要了,而且会失效(关于具体的变化请查看 PHP手册页的Session handling functions文档之描述).

启用register_globals本身并无安全风险,但是它为跟踪用户输入和确保应用程序安全增加了难度. 因为一旦打开了register_globals,那么在全局名称空间和$_GET,$_POST或$_COOKIE的数组中,将会自动创建 GET,POST和COOKIE传递到PHP脚本的所有变量. 如果您的PHP程序利用了这些变量来作安全标识(例如很多程序员喜欢用COOKIE值来辨别用户身份),那么任何人都可以用通过URL所传递的数值来获取并假冒用户身份,显然这就不再具备安全性了.另外,一些粗枝大叶或没有责任心的程序员所写的PHP代码, 例如: ,也很可能让黑客或恶意使用者有机可乘,使他们得以利用register_globals的弱点来进行诸如代码注入或内存溢位等方式的攻击,从而很轻易地造成系统性的灾难. 

重要提示: 自PHP4.2.0起,PHP中的选项register_globals的默认值被设为off了,PHP社区鼓励大家不要依赖于这个选项,而用其它方法来替代,例如superglobals。 

在Apache的httpd.conf中VirtualHost的相应设置方法: 
php_admin_flag register_globals on 
或者: 
php_admin_value register_globals 1 
 
(6) magic_quotes_gpc: 令敏感字元转义 
——————————————————————————– 
magic_quotes_gpc = On 
magic_quotes_gpc = Off 
magic_quotes_gpc选项是php中的一个重要的安全设置, 当该选项为ON, 也就是打开的时候,所有从GET,POST,COOKIE传递过来的数据之中的特殊字元(如’”\等),以及NULL等元字符都会被自动的加上\以实现转义,这个选项使得SQL注入或者插入代码,以及XSS中引入字符串或者改变程序流程变得更加困难。 

在php.ini配置文件中是默认启用magic_quotes_gpc设置(即为On)的,这相当于自动对所提交的 GET,POST,COOKIE数据使用了addslashes()函数. 如果网站空间关闭了 magic_quotes_gpc设置, 那么PHP就不会在敏感字元前加上反斜杠(\), 即允许表单所提交的内容含有敏感字元,例如单引号(’)等等, 这就更容易让黑客或者恶意使用者有机会利用SQL的注入漏洞发动攻击.当然现在很多具备安全意识的程序员或数据库管理员都懂得如何防范SQL注入攻击,他们通常会在相应的程序代码或应用环境中加强安全防范, 但是无论如何, 在系统层级加强安全防范,始终都应该是LINUX系统管理员的不二责任. 

请注意: PHP程序代码中关于敏感字元的处理,可以用addslashes()来自动在敏感字元前添加反斜杠, 也可以用函数stripslashes()来去掉反斜线. 另外, 许多数据库本身也提供了针对 这种输入数据的处理功能. 例如在PHP版本的MySQL操作函数中, 就有一个调用数据库来处理输入数据的函数: mysql_real_escape_string(); 它可将特殊字符以及可能引起数据库操作 出错的字符转义.在Apache的httpd.conf中VirtualHost的相应设置方法: 

php_admin_flag magic_quotes_gpc on 
或者: 
php_admin_value magic_quotes_gpc 1 

(7) allow_url_fopen和allow_url_include: 禁止读取远程文件 
——————————————————————————– 
allow_url_fopen = On (允许打开URL文件,预设启用) 
allow_url_fopen = Off (禁止打开URL文件) 
allow_url_include = Off (禁止引用URL文件,新版增加功能,预设关闭) 
allow_url_include = On (允许引用URL文件,新版增加功能) 
allow_url_fopen 这个命令选项启动了URL形式的fopen封装协议, 使得PHP程序可以连接URL对象(如远端文件). 预定的封装协议提供用ftp和http协议来连接远程文件,一些扩展库例如 zlib可能会注册更多的封装协议. allow_url_include预计是下一个PHP版本将要提供的功能,用来分离fopen和include函数的远端调用,现在PHP5.20已经提供了这个选项. 

就性能方面来说,PHP所提供的方便的远程调用确实简化了很多应用, 但若是从安全角度来看,允许引用(Include)URL远端资源,使得PHP应用程序的漏洞变得更加容易被利用, 这种方便性反而被很多安全研究人员视为一种漏洞(Remote URL Include vulnerabilities), 因此常常被建议必须在php.ini配置中禁止使用. 

PHP的开发者计划在PHP6版本中提供allow_url_include,现在这个功能已经可以在PHP5.20版中应用. 禁止allow_url_include解决了远端引用(Include)的问题, 同时又让我们还可以在一般的情形下使用fopen去打开远端的档案, 而不必再牵连上打开include函数所带来的风险.因此在新版PHP中allow_url_fopen选项预设是打开的,而allow_url_include则预设是关闭的. 

然而事实上若从系统角度来看,即使禁止了PHP的allow_url_fopen和allow_url_include功能,其实也不能完全阻止远端调用及其所带来的安全隐忧,而且它们只是保护了标记为URL的句柄,也就是说只能影响http(s)和ftp(s)的调用, 但对包含其他标记的远端调用,例如对PHP5.2.0新版所提供的php和data则无能为力,而这些调用一样会导致注入风险,请参考如下代码: 
<?php // Insecure Include 
// The following Include statement will 
// include and execute everything POSTed 
// to the server 
include “php://input”; 
?> 

<?php // Insecure Include 
// The following Include statement will 
// include and execute the base64 encoded 
// payload. Here this is just phpinfo() 
include “data:;base64,PD9waHAgcGhwaW5mbygpOz8+”; 
?> 

当然在LINUX的系统层级上,还有别的办法可用来防止远端调用, 例如使用IPTABLES等防火墙工具来保护系统,另外PHP应用程序也可以考虑采用curl来读取远程文件. 

请注意: 只在PHP4.0.3之后的版本中才可以在php.ini配置文件中使用allow_url_fopen选项,在PHP4.0.3以及之前的版本, 则只能在编译时通过配置项 –disable-url-fopen-wrapper来取消此特性. Windows下的PHP在4.3版本之前,相关函式: include, include_once, require,require_once 不支援远程文件连接,在PHP4.3版本之后才可以让这类函式有远端读取的能力. 

在Apache的httpd.conf中VirtualHost的相应设置方法: 
php_admin_flag allow_url_fopen Off 
php_admin_flag allow_url_include Off 
或者: 
php_admin_value allow_url_fopen 0 
php_admin_value allow_url_include 0 

(8) Error handling and logging: 错误控制和日志 
——————————————————————————– 
display_errors = On (打开错误显示) 
display_errors = Off (关闭错误显示) 
PHP缺省是打开错误信息显示的,如果把它改为关闭之后, 那么当PHP函数执行时,其错误信息将不会再显示给用户,这样能在一定程度上防止攻击者从错误信息得知脚本的物理位置,以及一些其它有用的信息,起码给攻击者的黑箱检测造成一定的障碍. 

如果PHP的错误信息对我们自己有用,也可以设置成把它写到日志文件中去,例如: 
log_errors = Off (PHP 默认是关闭错误日志的) 
log_errors = On (修改为打开并记录错误日志) 

如果打开了日志记录,接着还需要指定日志文件,即告诉PHP将错误记录到那个文件中去: 
;error_log = filename (默认被分号”;”所注释,修改为如下) 
error_log = /var/log/php_error.log 
就是把filename改为指定文件”/var/log/php_error.log”, 这样设置以后,所有的PHP错误都将会写到这个日志文件裡去。 
 
在Apache的httpd.conf中VirtualHost的相应设置方法: 
php_admin_flag display_errors Off 
或者: 
php_admin_value display_errors 0 
为方便区分,可在每个用户的VirtualHost中指定错误日志文件: 
ErrorLog logs/mydomain.com-error_log 

PHP安全配置范例
php.ini的安全设置范例: 
safe_mode = On 
allow_url_fopen = Off 
allow_url_include = Off 
register_globals = Off 
magic_quotes_gpc = On 
display_errors = Off 

disable_functions = shell_exec,system,exec,passthru,show_source,get_cfg_var 
#或者,也可以考虑开放后两个危险系数较低的函数: 
disable_functions = shell_exec,system,exec,passthru 

open_basedir = . 

VirtualHost的一个配置范例: 
——————————————————————————– 
<VirtualHost ip_addr:80> 
ServerAdmin [email]webmaster@mydomain[/email] 
DocumentRoot /home/hosting/mydomain/public_html 
ServerName mydomain.com 
ServerAlias www.mydomain.com 
php_admin_value safe_mode 1 
php_admin_value allow_url_fopen 0 
php_admin_value allow_url_include 0 
php_admin_value register_globals 1 
php_admin_value magic_quotes_gpc 1 
php_admin_value display_errors 0 
php_admin_value open_basedir /home/hosting/mydomain/ 
ErrorLog logs/mydomain.com-error_log 
CustomLog logs/mydomain.com-access_log common 
</VirtualHost> 

#或者: 
<VirtualHost ip_addr:80> 
ServerAdmin [email]webmaster@mydomain[/email] 
DocumentRoot /home/hosting/hung25ucom/public_html 
ServerName mydomain.com 
ServerAlias www.mydomain.com 
php_admin_flag safe_mode On 
php_admin_flag allow_url_fopen Off 
php_admin_flag allow_url_include Off 
php_admin_flag register_globals On 
php_admin_flag magic_quotes_gpc On 
php_admin_flag display_errors Off 
php_admin_value open_basedir /home/hosting/mydomain/ 
ErrorLog logs/mydomain.com-error_log 
CustomLog logs/mydomain.com-access_log common 
</VirtualHost>

利用php进行ddos攻击防范
表现特征:
一打开Apache服务器的流出带宽就用光,就是说服务器不断向别人发包,这个情况和受到DDOS攻击是不同的,DDOS是不断收到大量数据包。尤其是织梦dede一堆的漏洞,上传的这个当然是它的一个特色漏洞。


如何发现:
讲下笔记在debian5+apache+php环境的dede cms下的处理过程吧。
由于攻击者是通过web访问的方式来触发攻击行为的,攻击一般会持续两到三分钟,为了抓住是从哪一个站下发动的攻击,就就需要得知被攻击者的ip地址;为此,通过'iftop -n -i eth0'来观察外网口的流量情况(当然你也可以用其它即时流量统计工具,更多请参考本站此文章),如果发现有一个ip的流量一下子特别高,且持续好一会的话,那么它就是受害者。记下那个ip,然后在web的访问日志文件中将其找出(grep ip_addr /path/*.log),顺藤摸瓜,可以得到那个攻击脚本所在的站点的那个目录,然后在做处理。


攻击代码的核心部分:
//设置
set_time_limit(984918);
$host = $_GET['host'];
$port = $_GET['port'];
$exec_time = $_GET['time'];
$Sendlen = 65535;
$packets = 0;
ignore_user_abort(True);
….
while(1){
    $packets++;
    if(time() > $max_time){
        break;
    }
    $fp = fsockopen("udp://$host", $port, $errno, $errstr, 5);
        if($fp){
            fwrite($fp, $out);
            fclose($fp);
    }
}
…...


解决办法:
禁止上述的代码:
php.ini里设置:
disable_functions =gzinflate, fsockopen
hp.ini里设其值为Off
allow_url_fopen = Off

并且:
;extension=php_sockets.dll
前面的‘;’号一定要有,意思就是限制用sockets.dll,前面的;号要保留。然后重启web服务器或php-cgi,如果上述方式仍然无效,你可以在防火墙策略里禁止向外发送udp包。

注:linux下也可以通过禁用'sockets'扩展来达到此目的,当然也有其它的办法:
禁止本机对外发送UDP包 
iptables -A OUTPUT -p udp -j DROP 

允许需要UDP服务的端口(如DNS) 
iptables -I OUTPUT -p udp --dport 53 -d 8.8.8.8 -j ACCEPT 
“53”为DNS所需要的UDP端口,“8.8.8.8”部分为DNS IP,根据您服务器的设置来定。