PHP安全-過(guò)濾輸入
過(guò)濾是Web應(yīng)用安全的基礎(chǔ)。它是你驗(yàn)證數(shù)據(jù)合法性的過(guò)程。通過(guò)在輸入時(shí)確認(rèn)對(duì)所有的數(shù)據(jù)進(jìn)行過(guò)濾,你可以避免被污染(未過(guò)濾)數(shù)據(jù)在你的程序中被誤信及誤用。大多數(shù)流行的PHP應(yīng)用的漏洞最終都是因?yàn)闆](méi)有對(duì)輸入進(jìn)行恰當(dāng)過(guò)濾造成的。
我所指的過(guò)濾輸入是指三個(gè)不同的步驟:
l識(shí)別輸入
l過(guò)濾輸入
l區(qū)分已過(guò)濾及被污染數(shù)據(jù)
把識(shí)別輸入做為第一步是因?yàn)槿绻悴恢浪鞘裁矗阋簿筒荒苷_地過(guò)濾它。輸入是指所有源自外部的數(shù)據(jù)。例如,所有發(fā)自客戶端的是輸入,但客戶端并不是唯一的外部數(shù)據(jù)源,其它如數(shù)據(jù)庫(kù)和RSS推送等也是外部數(shù)據(jù)源。
由用戶輸入的數(shù)據(jù)非常容易識(shí)別,PHP用兩個(gè)超級(jí)公用數(shù)組$_GET 和$_POST來(lái)存放用戶輸入數(shù)據(jù)。其它的輸入要難識(shí)別得多,例如,$_SERVER數(shù)組中的很多元素是由客戶端所操縱的。常常很難確認(rèn)$_SERVER數(shù)組中的哪些元素組成了輸入,所以,最好的方法是把整個(gè)數(shù)組看成輸入。
在某些情況下,你把什么作為輸入取決于你的觀點(diǎn)。例如,session數(shù)據(jù)被保存在服務(wù)器上,你可能不會(huì)認(rèn)為session數(shù)據(jù)是一個(gè)外部數(shù)據(jù)源。如果你持這種觀點(diǎn)的話,可以把session數(shù)據(jù)的保存位置是在你的軟件的內(nèi)部。意識(shí)到session的保存位置的安全與軟件的安全是聯(lián)系在一起的事實(shí)是非常明智的。同樣的觀點(diǎn)可以推及到數(shù)據(jù)庫(kù),你也可以把它看成你軟件的一部分。
一般來(lái)說(shuō),把session保存位置與數(shù)據(jù)庫(kù)看成是輸入是更為安全的,同時(shí)這也是我在所有重要的PHP應(yīng)用開(kāi)發(fā)中所推薦的方法。
一旦識(shí)別了輸入,你就可以過(guò)濾它了。過(guò)濾是一個(gè)有點(diǎn)正式的術(shù)語(yǔ),它在平時(shí)表述中有很多同義詞,如驗(yàn)證、清潔及凈化。盡管這些大家平時(shí)所用的術(shù)語(yǔ)稍有不同,但它們都是指的同一個(gè)處理:防止非法數(shù)據(jù)進(jìn)入你的應(yīng)用。
有很多種方法過(guò)濾數(shù)據(jù),其中有一些安全性較高。最好的方法是把過(guò)濾看成是一個(gè)檢查的過(guò)程。請(qǐng)不要試圖好心地去糾正非法數(shù)據(jù),要讓你的用戶按你的規(guī)則去做,歷史證明了試圖糾正非法數(shù)據(jù)往往會(huì)導(dǎo)致安全漏洞。例如,考慮一下下面的試圖防止目錄跨越的方法(訪問(wèn)上層目錄)。
CODE:
<?php
$filename = str_replace(’..’, ’.’, $_POST[’filename’]);
?>
你能想到$_POST[’filename’]如何取值以使$filename成為L(zhǎng)inux系統(tǒng)中用戶口令文件的路徑../../etc/passwd嗎?
答案很簡(jiǎn)單:
.../.../etc/passwd
這個(gè)特定的錯(cuò)誤可以通過(guò)反復(fù)替換直至找不到為止:
CODE:
<?php
$filename = $_POST[’filename’];
while (strpos($_POST[’filename’], ’..’) != = FALSE)
{
$filename = str_replace(’..’, ’.’, $filename);
}
?>
當(dāng)然,函數(shù)basename( )可以替代上面的所有邏輯,同時(shí)也能更安全地達(dá)到目的。不過(guò)重要點(diǎn)是在于任何試圖糾正非法數(shù)據(jù)的舉動(dòng)都可能導(dǎo)致潛在錯(cuò)誤并允許非法數(shù)據(jù)通過(guò)。只做檢查是一個(gè)更安全的選擇。
譯注:這一點(diǎn)深有體會(huì),在實(shí)際項(xiàng)目曾經(jīng)遇到過(guò)這樣一件事,是對(duì)一個(gè)用戶注冊(cè)和登錄系統(tǒng)進(jìn)行更改,客戶希望用戶名前后有空格就不能登錄,結(jié)果修改時(shí)對(duì)用戶登錄程序進(jìn)行了更改,用trim()函數(shù)把輸入的用戶名前后的空格去掉了(典型的好心辦壞事),但是在注冊(cè)時(shí)居然還是允許前后有空格!結(jié)果可想而知。
除了把過(guò)濾做為一個(gè)檢查過(guò)程之外,你還可以在可能時(shí)用白名單方法。它是指你需要假定你正在檢查的數(shù)據(jù)是非法的,除非你能證明它是合法的。換而言之,你寧可在小心上犯錯(cuò)。使用這個(gè)方法,一個(gè)錯(cuò)誤只會(huì)導(dǎo)致你把合法的數(shù)據(jù)當(dāng)成是非法的。盡管不想犯任何錯(cuò)誤,但這樣總比把非法數(shù)據(jù)當(dāng)成合法數(shù)據(jù)要安全得多。通過(guò)減輕犯錯(cuò)引起的損失,你可以提高你的應(yīng)用的安全性。盡管這個(gè)想法在理論上是很自然的,但歷史證明,這是一個(gè)很有價(jià)值的方法。
如果你能正確可靠地識(shí)別和過(guò)濾輸入,你的工作就基本完成了。最后一步是使用一個(gè)命名約定或其它可以幫助你正確和可靠地區(qū)分已過(guò)濾和被污染數(shù)據(jù)的方法。我推薦一個(gè)比較簡(jiǎn)單的命名約定,因?yàn)樗梢酝瑫r(shí)用在面向過(guò)程和面向?qū)ο蟮木幊讨小N矣玫拿s定是把所有經(jīng)過(guò)濾的數(shù)據(jù)放入一個(gè)叫$clean的數(shù)據(jù)中。你需要用兩個(gè)重要的步驟來(lái)防止被污染數(shù)據(jù)的注入:
l經(jīng)常初始化$clean為一個(gè)空數(shù)組。
l加入檢查及阻止來(lái)自外部數(shù)據(jù)源的變量命名為clean,
實(shí)際上,只有初始化是至關(guān)緊要的,但是養(yǎng)成這樣一個(gè)習(xí)慣也是很好的:把所有命名為clean的變量認(rèn)為是你的已過(guò)濾數(shù)據(jù)數(shù)組。這一步驟合理地保證了$clean中只包括你有意保存進(jìn)去的數(shù)據(jù),你所要負(fù)責(zé)的只是不在$clean存在被污染數(shù)據(jù)。
為了鞏固這些概念,考慮下面的表單,它允許用戶選擇三種顏色中的一種;
CODE:
<form action='process.php' method='POST'>
Please select a color:
<select name='color'>
<option value='red'>red</option>
<option value='green'>green</option>
<option value='blue'>blue</option>
</select>
<input type='submit' />
</form>
在處理這個(gè)表單的編程邏輯中,非常容易犯的錯(cuò)誤是認(rèn)為只能提交三個(gè)選擇中的一個(gè)。在第二章中你將學(xué)到,客戶端能提交任何數(shù)據(jù)作為$_POST[’color’]的值。為了正確地過(guò)濾數(shù)據(jù),你需要用一個(gè)switch語(yǔ)句來(lái)進(jìn)行:
CODE:
<?php
$clean = array( );
switch($_POST[’color’])
{
case ’red’:
case ’green’:
case ’blue’:
$clean[’color’] = $_POST[’color’];
break;
}
?>
本例中首先初始化了$clean為空數(shù)組以防止包含被污染的數(shù)據(jù)。一旦證明$_POST[’color’]是red, green, 或blue中的一個(gè)時(shí),就會(huì)保存到$clean[’color’]變量中。因此,可以確信$clean[’color’]變量是合法的,從而在代碼的其它部分使用它。當(dāng)然,你還可以在switch結(jié)構(gòu)中加入一個(gè)default分支以處理非法數(shù)據(jù)的情況。一種可能是再次顯示表單并提示錯(cuò)誤。特別小心不要試圖為了友好而輸出被污染的數(shù)據(jù)。
上面的方法對(duì)于過(guò)濾有一組已知的合法值的數(shù)據(jù)很有效,但是對(duì)于過(guò)濾有一組已知合法字符組成的數(shù)據(jù)時(shí)就沒(méi)有什么幫助。例如,你可能需要一個(gè)用戶名只能由字母及數(shù)字組成:
CODE:
<?php
$clean = array( );
if (ctype_alnum($_POST[’username’]))
{
$clean[’username’] = $_POST[’username’];
}
?>
盡管在這種情況下可以用正則表達(dá)式,但使用PHP內(nèi)置函數(shù)是更完美的。這些函數(shù)包含錯(cuò)誤的可能性要比你自已寫(xiě)的代碼出錯(cuò)的可能性要低得多,而且在過(guò)濾邏輯中的一個(gè)錯(cuò)誤幾乎就意味著一個(gè)安全漏洞。
相關(guān)文章:
1. asp讀取xml文件和記數(shù)2. vue實(shí)現(xiàn)移動(dòng)端返回頂部3. PHP實(shí)現(xiàn)基本留言板功能原理與步驟詳解4. CSS自定義滾動(dòng)條樣式案例詳解5. xml中的空格之完全解說(shuō)6. Python如何實(shí)現(xiàn)感知器的邏輯電路7. 多個(gè)SpringBoot項(xiàng)目采用redis實(shí)現(xiàn)Session共享功能8. python利用opencv實(shí)現(xiàn)顏色檢測(cè)9. vue 驗(yàn)證兩次輸入的密碼是否一致的方法示例10. python基于scrapy爬取京東筆記本電腦數(shù)據(jù)并進(jìn)行簡(jiǎn)單處理和分析
