Linux下的Apache和PHP安全设置简要

Linux下的Apache和PHP安全设置简要

说明: LAMP系统以安全和高效而闻名,但缺省的安装配置依然有部分权限设置过于宽松,导致
系统存在安全隐患, 尤其是对于提供支持PHP功能的虚拟域名空间租用服务的系统,就一定要
加强相关的安全设置.

本文原来是工作过程中的笔记,主要是针对RedHat Linux版本的设置特点,所介绍的配置选项
部分是从Google上寻找,部分是我的经验心得,均在CentOS4.4中调试通过. 讀者如有兴趣,请
参考使用; 若有更好更全面的见解,也欢迎不吝赐教(chengkinhung@gmail.com);

PHP安全设置的参考资料: (網主Stefan Esser原是teso成员,是目前搞php安全最牛的人之一)
http://www.hardened-php.net/hardening_patch.14.html


################################################################################
請注意: 本文所描述的选项配置只涉及两个文件: php.ini和httpd.conf;

參考網址: http://us2.php.net/configuration.changes

對于提供公共網絡服務的ISP,基于安全性理由,建議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版本)

請注意: 上述針對虛擬用戶的設置命令,可以直接設置在<VirtualHost>單元里面,或者設置在
相應用戶的<Directory>單元里面.

例如,要将ernest这个用户的register_globals功能打开,并且将upload_max_filesize调高到
5M,同時關閉safe_mode,但又不影响其他用户,就可以在该用户的VirtualHost里面加如下幾行:

<VirtualHost 222.222.222.222>
        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,用以防止合法用戶的跨站讀取或越權操作等
危險行為, 以及將非授權用戶的惡意行為所造成的影響降到最低范圍.

參考網址: http://hk2.php.net/features.safe-mode

在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如何獲取從<form>的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代码, 例如: <? include($value); ?>,
也很可能讓黑客或惡意使用者有機可乘,使他們得以利用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+";
?>

上述代碼來自網絡,請參考如下網址:
http://blog.php-security.org/arc ... ow_url_include.html

當然在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 222.222.222.222:80>
    ServerAdmin webmaster@mydomain
    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 222.222.222.222:80>
    ServerAdmin webmaster@mydomain
    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>
--------------------------------------------------------------------------------
================================================================================

      
楼主厉害,一时间看得好晕。      
不要用  繁体字!  我们 看不懂      
同意楼上的说法,不过自己复制下来转一下也行