寫給系統管理員的25個PHP安全實踐
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提供簡單的負載均衡。