PHP是广泛使用的开源服务端脚本语言。通过HTTP或HTTPS协议,Apache Web服务允许用户访问文件或内容。服务端脚本语言的错误配置会导致各种问题。因此,PHP应该小心使用。以下是为系统管理员准备的,安全配置PHP的25个实践事例。
用于下文的PHP设置样例
- DocumentRoot:/var/www/html
- 默认Web服务:Apache(可以使用Lighttpd或Nginx代替)
- 默认PHP配置文件:/etc/php.ini
- 默认PHP Extensions配置目录:/etc/php.d/
- PHP安全配置样例文件:/etc/php.d/security.ini(需要使用文本编辑器创建这个文件)
- 操作系统:RHEL / CentOS / Fedora Linux(指令应该可以在所有其他Linux发行版,如Debian / Ubuntu,或是Unix-like的操作系统,如OpenBSD / FreeBSD / HP-UX下正常运行)
- PHP服务的默认TCP/UDP端口:none
下午列出的大部分操作,都是基于 root 用户能在 bash 或其他现代 shell 上执行操作的假设。
- $ php -v
样例输出:
- PHP 5.3.3 (cli) (built: Oct 24 2011 08:35:41)
- Copyright (c) 1997-2010 The PHP Group
- Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
本文使用的操作系统:
- $ cat /etc/redhat-release
样例输出:
- Red Hat Enterprise Linux Server release 6.1 (Santiago)
#1:知彼
基于PHP的应用面临着各种各样的攻击:
- XSS:对PHP的Web应用而言,跨站脚本是一个易受攻击的点。攻击者可以利用它盗取用户信息。你可以配置Apache,或是写更安全的PHP代码(验证所有用户输入)来防范XSS攻击
- SQL注入:这是PHP应用中,数据库层的易受攻击点。防范方式同上。常用的方法是,使用mysql_real_escape_string()对参数进行转义,而后进行SQL查询。
- 文件上传:它可以让访问者在服务器上放置(即上传)文件。这会造成例如,删除服务器文件、数据库,获取用户信息等一系列问题。你可以使用PHP来禁止文件上传,或编写更安全的代码(如检验用户输入,只允许上传png、gif这些图片格式)
- 包含本地与远程文件:攻击者可以使远程服务器打开文件,运行任何PHP代码,然后上传或删除文件,安装后门。可以通过取消远程文件执行的设置来防范
- eval():这个函数可以使一段字符串如同PHP代码一样执行。它通常被攻击者用于在服务器上隐藏代码和工具。通过配置PHP,取消eval()函数调用来实现
- Sea-surt Attack(Cross-site request forgery,CSRF。跨站请求伪造):这种攻击会使终端用户在当前账号下执行非指定行为。这会危害终端用户的数据与操作安全。如果目标终端用户的账号用于管理员权限,整个Web应用都会收到威胁。
#2:减少内建的PHP模块
执行下面指令可以查看当前PHP所编译的模块:
- $ php -m
样例输出:
- [PHP Modules]
- apc
- bcmath
- bz2
- calendar
- Core
- ctype
- curl
- date
- dom
- ereg
- exif
- fileinfo
- filter
- ftp
- gd
- gettext
- gmp
- hash
- iconv
- imap
- json
- libxml
- mbstring
- memcache
- mysql
- mysqli
- openssl
- pcntl
- pcre
- PDO
- pdo_mysql
- pdo_sqlite
- Phar
- readline
- Reflection
- session
- shmop
- SimpleXML
- sockets
- SPL
- sqlite3
- standard
- suhosin
- tokenizer
- wddx
- xml
- xmlreader
- xmlrpc
- xmlwriter
- xsl
- zip
- zlib
- [Zend Modules]
- Suhosin
从性能与安全性的角度考虑,我建议使用PHP时减少不必要的模块。例如上面的sqlite3是不必要的。那么可以通过删除或重命名/etc/php.d/sqlite3.ini文件来取消它:
- # rm /etc/php.d/sqlite3.ini
或
- # mv /etc/php.d/sqlite3.ini /etc/php.d/sqlite3.disable
有些模块则只能通过使用重新编译安装PHP来移除。例如,从php.net下载PHP源码后,使用下面指令编译GD,fastcgi和MySQL支持:
- ./configure --with-libdir=lib64 --with-gd --with-mysql --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libexecdir=/usr/libexec --localstatedir=/var --sharedstatedir=/usr/com --mandir=/usr/share/man --infodir=/usr/share/info --cache-file=../config.cache --with-config-file-path=/etc --with-config-file-scan-dir=/etc/php.d --enable-fastcgi --enable-force-cgi-redirect
更多信息请查看:how to compile and reinstall php on Unix like operating system
#p#
#3:防止PHP信息泄漏
可以通过取消export_php,对PHP信息泄漏进行限制。编辑/etc/php.d/security.ini如下:
- expose_php=Off
expose_php会在HTTP Header中添加服务器上,包括版本在内的PHP信息(例如X-Powered-By: PHP/5.3.3)。同时,PHP的全局统一标识符也会暴露。如果export_php启用的话,可以通过下面命令查看PHP版本信息:
- $ curl -I http://www.cyberciti.biz/index.php
样例输出:
- HTTP/1.1 200 OK
- X-Powered-By: PHP/5.3.3
- Content-type: text/html; charset=UTF-8
- Vary: Accept-Encoding, Cookie
- X-Vary-Options: Accept-Encoding;list-contains=gzip,Cookie;string-contains=wikiToken;string-contains=wikiLoggedOut;string-contains=wiki_session
- Last-Modified: Thu, 03 Nov 2011 22:32:55 GMT
- ...
建议同时隐藏Apache版本等信息:ServerTokens and ServerSignature directives in httpd.conf to hide Apache version
#4:最小化可载入的PHP模块(动态Extension)
PHP支持“Dynamic Extensions”。默认情况下,RHEL会载入/etc/php.d/目录下的所有Extension模块。如需启用或取消某一模块,只需把/etc/php.d/目录下配置文件把该模块注释掉。也可以把文件删除或重命名该模块的配置文件。为了最优化PHP的性能和安全性,应只启用Web应用所需的Extension。例如,用下面命令取消GD模块:
- # cd /etc/php.d/
- # mv gd.{ini,disable}
- # <span style="text-decoration: underline;">/sbin/service httpd restart</span>
启用则是:
- # mv gd.{disable,ini}
- # <span style="text-decoration: underline;">/sbin/service httpd restart</span>
#5:记录所有PHP错误
不要把PHP错误信息输出给所用用户。编辑/etc/php.d/security.ini,如下修改:
- display_errors=Off
确保把所有错误信息记录到日志文件:
- log_errors=On
- error_log=/var/log/httpd/php_scripts_error.log
#6:禁止文件上传
为安全考虑,如下编辑/etc/php.d/security.ini取消文件上传
- file_uploads=Off
如用户的确需要上传文件,那么把它启用,而后限制PHP接受的最大文件大小:
- file_uploads=On
- # user can only upload upto 1MB via php
- upload_max_filesize=1M
#7:关闭远程代码执行
如果这个特性被启动,PHP可以通过allow_url_fopen,在file_get_contents()、include、require中获取诸如FTP或网页内容这些远程数据。程序员经常忘记了对用户输入进行过滤,而如果这些函数调用了这些数据,则形成了注入漏洞。在基于PHP的Web应用中,大量代码中的注入漏洞都由此产生。可以通过编辑/etc/php.d/security.ini来关闭该特性:
- allow_url_fopen=Off
- 除此之外,建议把allow_url_include也取消掉:
- allow_url_include=Off
#8:启用SQL安全模式
如下修改/etc/php.d/security.ini:
- sql.safe_mode=On
当此特性被启用,mysql_connect()和mysql_pconnect()会忽略传入的所有参数。与此同时,你需要在代码上做些相应的修改。第三方以及开源应用,如Wordpress,在sql.safe_mode下可能无法正常工作。同时建议关闭5.3.x版本的PHP的magic_quotes_gpc过滤,因为它简单粗暴又没效率。使用mysql_escape_string()以及自定义的过滤函数会更好一些
- magic_quotes_gpc=Off
#9:控制POST的数据大小
HTTP POST通常作为请求的一部分,被客户端用于向Apache Web服务器发送数据,如上传文件或提交表单。攻击者会尝试发送超大的POST请求去消耗服务器的资源。如下编辑/etc/php.d/security.ini限制POST的最大大小:
- ; 在这里设置一个靠谱的数值
- post_max_size=1K
这里设置了1K的最大大小。这个设置会影响到文件上传。要上传大文件,这个值需要比update_max_filesize大。
建议在Apache中限制可用的请求方法,编辑httpd.conf如下:
- <Directory /var/www/html>
- <LimitExcept GET POST>
- Order allow,deny
- </LimitExcept>
- ## Add rest of the config goes here... ##
- </Directory>
#10:资源控制(DoS控制)
设置每个PHP脚本的最大运行时间。另外建议限制用于处理请求数据的最大时间,以及最大可用内存数。
- # 单位:秒
- max_execution_time = 30
- max_input_time = 30
- memory_limit = 40M
#11:为PHP安装Suhosin高级保护系统
具体参考Suhosin项目页:project page
#12:取消危险的PHP函数
PHP有大量可用于入侵服务器的函数,如使用不当则会成为漏洞。如下取消这些函数:
- disable_functions =exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
#13:PHP Fastcgi / CGI – cgi.force_redirect管理
PHP可与Fastcgi协同工作。Fastcgi可以减少Web服务器的内存足迹(memory footprint),并改善PHP性能。可以参考这个来配置Apache2+PHP+FastCGI。在这个配置中,cgi.force_redirect会阻止用户通过访问URL来调用PHP。为安全考虑,启用该特性:
- ; Enable cgi.force_redirect for security reasons in a typical *Apache+PHP-CGI/FastCGI* setup
- cgi.force_redirect=On
#14:PHP用户与用户组ID
mod_fastcgi是Apache Web服务的一个cgi模块,可连接到外部的FASTCGI服务器。你需要确保PHP使用非root用户运行。若其使用root或是UID小于100的用户权限,它就可以访问,乃至操作系统文件。通过Apache’s suEXEC或mod_suPHP,可在非特权用户下执行PHP CGI。suEXEC可以是Apache调用CGI程序的user ID不同于运行Apache的user ID。如下:
- # ps aux | grep php-cgi
样例输出:
- phpcgi 6012 0.0 0.4 225036 60140 S Nov22 0:12 /usr/bin/php-cgi
- phpcgi 6054 0.0 0.5 229928 62820 S Nov22 0:11 /usr/bin/php-cgi
- phpcgi 6055 0.1 0.4 224944 53260 S Nov22 0:18 /usr/bin/php-cgi
- phpcgi 6085 0.0 0.4 224680 56948 S Nov22 0:11 /usr/bin/php-cgi
- phpcgi 6103 0.0 0.4 224564 57956 S Nov22 0:11 /usr/bin/php-cgi
- phpcgi 6815 0.4 0.5 228556 61220 S 00:52 0:19 /usr/bin/php-cgi
- phpcgi 6821 0.3 0.5 228008 61252 S 00:55 0:12 /usr/bin/php-cgi
可以通过spawn-fcgi来生成phpcgi用户的远程或本地FastCGI进程(前提是有这个用户):
- # spawn-fcgi -a 127.0.0.1 -p 9000 -u phpcgi -g phpcgi -f /usr/bin/php-cgi
现在可以配置Apache、Lighthttpd或Nginx Web服务调用运行在127.0.0.1:9000的FastCGI。
#p#
#15:限制PHP访问文件系统
open_basedir会限制PHP的运行目录,例如通过fopen()之类的函数可访问的目录。如果访问的目录不在open_basedir之内,PHP会拒绝该访问。不要使用软链接作为工作区。例如,只允许访问/var/www/html而非/var/www、/tmp或/etc目录:
- ; Limits the PHP process from accessing files outside
- ; of specifically designated directories such as /var/www/html/
- open_basedir="/var/www/html/"
- ; ------------------------------------
- ; Multiple dirs example
- ; open_basedir="/home/httpd/vhost/cyberciti.biz/html/:/home/httpd/vhost/nixcraft.com/html/:/home/httpd/vhost/theos.in/html/"
- ; ------------------------------------
#16:Session路径
PHP Session用户提供数据保存功能,以便后续访问。这可以使应用可定制性更强,提升吸引力。所有Session相关的数据会被保存在session.save_path中。RHEL/CentOS/Fedora Linux的默认设置如下:
- session.save_path="/var/lib/php/session"
- ; Set the temporary directory used for storing files when doing file upload
- upload_tmp_dir="/var/lib/php/session"
确认这个路径在/var/www/html之外,且不可被其他系统用户访问:
- # ls -Z /var/lib/php/
样例输出:
- drwxrwx---. root apache system_u:object_r:httpd_var_run_t:s0 session
注:ls -Z会显示SELinux的安全信息,如文件模式,user,group,安全信息,文件名等。
#17:保证PHP,软件及操作系统更新到最新
维护Linux、Apache、PHP和MySQL服务器的一项重要工作是更新安全补丁。所有的PHP安全更新应尽快进行审查并更新。可使用如下命令(如果通过包管理器来安装PHP):
- # yum update
- 或
- # apt-get update && apt-get upgrade
可以配置Red Hat / CentOS / Fedora Linux通过Email发送yum的包更新提醒,或是Debian / Ubuntu Linux下的apticron发送提醒。又或通过cron计划任务进行更新。
注:查看php.net以获取最新的PHP版本信息
#18:限制文件及目录访问
确认以Apache或www这种非root用户运行Apache。/var/www/html目录下的owner也应是非root用户:
- # chown -R apache:apache /var/www/html/
DocumentRoot下的文件应禁止运行或创建。设置该目录下的文件权限为0444(只读):
- # chmod -R 0444 /var/www/html/
设置该目录下的所有文件夹权限为0445:
- # find /var/www/html/ -type d -print0 | xargs -0 -I {} chmod 0445 {}
#19:Apache、PHP、MySQL配置文件的写入保护
使用chattr命令给这些配置文件加上写入保护:
- # chattr +i /etc/php.ini
- # chattr +i /etc/php.d/*
- # chattr +i /etc/my.ini
- # chattr +i /etc/httpd/conf/httpd.conf
- # chattr +i /etc/
同样可以为/var/www/html目录加上写入保护
- # chattr +i /var/www/html/file1.php# chattr +i /var/www/html/
#20:使用Linux安全拓展(如SELinux)
Linux有各种安全方案来防止服务程序的错误配置或漏洞。尽可能使用SELinux或其他Linux安全方案限制网络和程序。例如,SELinux为Linux内核或Apache Web服务提供不同的安全策略。使用下面命令列出所有Apache保护信息:
- # getsebool -a | grep httpd
样例输出:
- allow_httpd_anon_write --> off
- allow_httpd_mod_auth_ntlm_winbind --> off
- allow_httpd_mod_auth_pam --> off
- allow_httpd_sys_script_anon_write --> off
- httpd_builtin_scripting --> on
- httpd_can_check_spam --> off
- httpd_can_network_connect --> off
- httpd_can_network_connect_cobbler --> off
- httpd_can_network_connect_db --> off
- httpd_can_network_memcache --> off
- httpd_can_network_relay --> off
- httpd_can_sendmail --> off
- httpd_dbus_avahi --> on
- httpd_enable_cgi --> on
- httpd_enable_ftp_server --> off
- httpd_enable_homedirs --> off
- httpd_execmem --> off
- httpd_read_user_content --> off
- httpd_setrlimit --> off
- httpd_ssi_exec --> off
- httpd_tmp_exec --> off
- httpd_tty_comm --> on
- httpd_unified --> on
- httpd_use_cifs --> off
- httpd_use_gpg --> off
- httpd_use_nfs --> off
取消Apache cgi支持可以输入:
- # setsebool -P httpd_enable_cgi off
#p#
#21:安装Mod_security
ModSecurity是一个开源的入侵检测和防范的Web应用引擎。安装mod_security可以保护Apache和PHP应用免受XSS和其他攻击:
- ## A few Examples ##
- # Do not allow to open files in /etc/
- SecFilter /etc/
- # Stop SQL injection
- SecFilter "delete[[:space:]]+from"
- SecFilter "select.+from"
#22:如有可能,在Chroot Jail下运行Apache / PHP
在Chroot Jail下运行Apache / PHP可以最小化可能受到的损失,使其局限于文件系统下的一小块。可以使用一般的chroot来配置Apache:chroot kind of setup with Apache。不过我建议使用FreeBSD jails、XEN,KVM或OpenVZ虚拟化。
#23:使用防火墙限制传出连接
攻击者会使用wget之类的工具从你的Web服务器下载文件。使用iptables来阻挡Apache用户的传出连接。ipt_owner模块会为本地数据包的生成者分配不同角色。它只对OUTPUT chain有效。下面指令允许vivek用户通过80端口进行外部访问:
- /sbin/iptables -A OUTPUT -o eth0 -m owner --uid-owner vivek -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
下面的样例则是阻挡所有Apache用户的传出连接,只允许smtp服务及spam识别API服务通过:
- # ....
- /sbin/iptables --new-chain apache_user
- /sbin/iptables --append OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
- /sbin/iptables --append OUTPUT -m owner --uid-owner apache -j apache_user
- # allow apache user to connec to our smtp server
- /sbin/iptables --append apache_user -p tcp --syn -d 192.168.1.100 --dport 25 -j RETURN
- # Allow apache user to connec to api server for spam validation
- /sbin/iptables --append apache_user -p tcp --syn -d 66.135.58.62 --dport 80 -j RETURN
- /sbin/iptables --append apache_user -p tcp --syn -d 66.135.58.61 --dport 80 -j RETURN
- /sbin/iptables --append apache_user -p tcp --syn -d 72.233.69.89 --dport 80 -j RETURN
- /sbin/iptables --append apache_user -p tcp --syn -d 72.233.69.88 --dport 80 -j RETURN
- #########################
- ## Add more rules here ##
- #########################
- # No editing below
- # Drop everything for apache outgoing connection
- /sbin/iptables --append apache_user -j REJECT
#24:查看并审查日志
查看Apache日志文件:
- # tail -f /var/log/httpd/error_log
- # grep 'login.php' /var/log/httpd/error_log
- # egrep -i "denied|error|warn" /var/log/httpd/error_log
查看PHP日志文件:
- # tail -f /var/log/httpd/php_scripts_error.log
- # grep "...etc/passwd" /var/log/httpd/php_scripts_error.log
查看日志文件可以让你知道服务器正在承受何种攻击,并分析当前安全级别是否足够。启用审查服务用于系统审查,可审查SELinux时间,验证事件,文件修改,账号修改等。建议使用Linux System Monitoring Tools来监控Web服务器。
#25:把服务分离到不同的服务器或虚拟机
对于比较庞大的安装配置,建议把运行、数据库、静态与动态内容分离到不同的服务器
- ///////////////
- / ISP/Router /
- //////////////
- \
- |
- Firewall
- \
- |
- +------------+
- | LB01 |
- +------------+ +--------------------------+
- | | static.lan.cyberciti.biz |
- +-----------------+--------------------------+
- | phpcgi1.lan.cyberciti.biz|
- +--------------------------+
- | phpcgi2.lan.cyberciti.biz|
- +--------------------------+
- | mysql1.lan.cyberciti.biz |
- +--------------------------+
- | mcache1.lan.cyberciti.biz|
- +--------------------------+
在不同的服务器或虚拟机下运行不同的网络服务,这可以减少被入侵对其他服务的影响。例如,一个攻击者入侵了Apache,那就可以访问同一服务器下的其他服务(如MySQL,email服务等)。但在上述例子中则不会:
- static.lan.cybercity.biz – 使用lighttpd或nginx存放js/css/images等静态资源
- phpcgi1.lan.cyberciti.biz和phpcgi2.lan.cyberciti.biz – Apache Web服务+PHP,用于生成动态内容
- mysql1.lan.cyberciti.biz – MySQL数据库服务
- mcache1.lan.cyberciti.biz – Memcached服务(MySQL的高速缓存系统)。它使用libevent或epoll来适应任意连接数。而且它使用的是非阻塞网络IO。
- LB01 – 一个Nginx服务器,用于Web及Apache前端的反向代理。所有的访问连接会通过nginx代理服务,被直接处理或分发到相应的Web服务器。LB01提供简单的负载均衡。