在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(關閉安全模式)
當安全模式打開的時候,以下函數列表的功能將會受到限制:
函數名
限制
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 source
and target
)
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 變量
在安全模式下,變量 PHP_AUTH_USER
、PHP_AUTH_PW
和 PHP_AUTH_TYPE
在 $_SERVER
中不可用。但無論如何,您仍然可以使用 REMOTE_USER
來獲取用戶名稱(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函數進行轉義,或者使用其他方法