PHP安全程式設計之過濾使用者輸入

2021-07-16 09:45:58 字數 2767 閱讀 6510

過濾是web應用安全的基礎。它是你驗證資料合法性的過程。通過在輸入時確認對所有的資料進行過濾,你可以避免被汙染(未過濾)資料在你的程式中被誤信及誤用。大多數流行的php應用的漏洞最終都是因為沒有對輸入進行恰當過濾造成的。

我所指的過濾輸入是指三個不同的步驟:

把識別輸入作為第一步是因為如果你不知道它是什麼,你也就不能正確地過濾它。輸入是指所有源自外部的資料。例如,所有發自客戶端的是輸入,但客戶端並不是唯一的外部資料來源,其它如資料庫和rss推送等也是外部資料來源。

由使用者輸入的資料非常容易識別,php用兩個超級公用陣列$_get 和$_post來存放使用者輸入資料。其它的輸入要難識別得多,例如,$_server陣列中的很多元素是由客戶端所操縱的。常常很難確認$_server陣列中的哪些元素組成了輸入,所以,最好的方法是把整個陣列看成輸入。

在某些情況下,你把什麼作為輸入取決於你的觀點。例如,session資料被儲存在伺服器上,你可能不會認為session資料是乙個外部資料來源。如果你持這種觀點的話,可以把session資料的儲存位置是在你的軟體的內部。意識到session的儲存位置的安全與軟體的安全是聯絡在一起的事實是非常明智的。同樣的觀點可以推及到資料庫,你也可以把它看成你軟體的一部分。

一般來說,把session儲存位置與資料庫看成是輸入是更為安全的,同時這也是我在所有重要的php應用開發中所推薦的方法。

一旦識別了輸入,你就可以過濾它了。過濾是乙個有點正式的術語,它在平時表述中有很多同義詞,如驗證、清潔及淨化。儘管這些大家平時所用的術語稍有不同,但它們都是指的同乙個處理:防止非法資料進入你的應用。

有很多種方法過濾資料,其中有一些安全性較高。最好的方法是把過濾看成是乙個檢查的過程。請不要試圖好心地去糾正非法資料,要讓你的使用者按你的規則去做,歷史證明了試圖糾正非法資料往往會導致安全漏洞。例如,考慮一下下面的試圖防止目錄跨越的方法(訪問上層目錄)。

$filename = str_replace('..', '.', $_post['filename']);
你能想到$_post['filename']如何取值以使$filename成為linux系統中使用者口令檔案的路徑../../etc/passwd嗎?

答案很簡單:.../.../etc/passwd

這個特定的錯誤可以通過反覆替換直至找不到為止:

<?php

$filename = $_post['filename'];

while (strpos($_post['filename'], '..') !== false)

?>

當然,函式basename( )可以替代上面的所有邏輯,同時也能更安全地達到目的。不過重要點是在於任何試圖糾正非法資料的舉動都可能導致潛在錯誤並允許非法資料通過。只做檢查是乙個更安全的選擇。

乙個小故事:這一點深有體會,在實際專案曾經遇到過這樣一件事,是對乙個使用者註冊和登入系統進行更改,客戶希望使用者名稱前後有空格就不能登入,結果修改時對使用者登入程式進行了更改,用trim()函式把輸入的使用者名稱前後的空格去掉了(典型的好心辦壞事),但是在註冊時居然還是允許前後有空格!結果可想而知。

除了把過濾做為乙個檢查過程之外,你還可以在可能時用白名單方法。它是指你需要假定你正在檢查的資料是非法的,除非你能證明它是合法的。換而言之,你寧可在小心上犯錯。使用這個方法,乙個錯誤只會導致你把合法的資料當成是非法的。儘管不想犯任何錯誤,但這樣總比把非法資料當成合法資料要安全得多。通過減輕犯錯引起的損失,你可以提高你的應用的安全性。儘管這個想法在理論上是很自然的,但歷史證明,這是乙個很有價值的方法。

如果你能正確可靠地識別和過濾輸入,你的工作就基本完成了。最後一步是使用乙個命名約定或其它可以幫助你正確和可靠地區分已過濾和被汙染資料的方法。我推薦乙個比較簡單的命名約定,因為它可以同時用在面向過程和物件導向的程式設計中。我用的命名約定是把所有經過濾的資料放入乙個叫$clean的資料中。你需要用兩個重要的步驟來防止被汙染資料的注入:

實際上,只有初始化是至關緊要的,但是養成這樣乙個習慣也是很好的:把所有命名為clean的變數認為是你的已過濾資料陣列。這一步驟合理地保證了$clean中只包括你有意儲存進去的資料,你所要負責的只是不在$clean存在被汙染資料。

為了鞏固這些概念,考慮下面的表單,它允許使用者選擇三種顏色中的一種:

在處理這個表單的程式設計邏輯中,非常容易犯的錯誤是認為只能提交三個選擇中的乙個。為了正確地過濾資料,你需要用乙個switch語句來進行:

<?php

$clean = array();

switch($_post['color'])

?>

本例中首先初始化了$clean為空陣列以防止包含被汙染的資料。一旦證明$_post['color']是red, green, 或blue中的乙個時,就會儲存到$clean['color']變數中。因此,可以確信$clean['color']變數是合法的,從而在**的其它部分使用它。當然,你還可以在switch結構中加入乙個default分支以處理非法資料的情況。一種可能是再次顯示表單並提示錯誤。特別小心不要試圖為了友好而輸出被汙染的資料。

上面的方法對於過濾有一組已知的合法值的資料很有效,但是對於過濾有一組已知合法字元組成的資料時就沒有什麼幫助。例如,你可能需要乙個使用者名稱只能由字母及數字組成:

<?php

$clean = array();

if (ctype_alnum($_post['username']))

?>

儘管在這種情況下可以用正規表示式,但使用php內建函式是更完美的。這些函式包含錯誤的可能性要比你自已寫的**出錯的可能性要低得多,而且在過濾邏輯中的乙個錯誤幾乎就意味著乙個安全漏洞。

PHP安全程式設計之PHP的安全模式

php的safe mode選項的目的是為了解決本小節前後所述的某些問題。但是,在php層面上去解決這類問題從架構上來看是不正確的,正如php手冊所述 當安全模式生效時,php會對正在執行的指令碼所讀取 或所操作 檔案的屬主進行檢查,以保證與該指令碼的屬主是相同的。雖然這樣確實可以防範本章中的很多例子...

PHP安全程式設計之留心後門URL

後門url是指雖然無需直接呼叫的資源能直接通過url訪問。例如,下面web應用可能向登入使用者顯示敏感資訊 authenticated false authenticated check auth if authenticated 由於sensitive.php位於 主目錄下,用瀏覽器能跳過驗證機制...

PHP安全程式設計之shell命令注入

使用系統命令是一項危險的操作,尤其在你試圖使用遠端資料來構造要執行的命令時更是如此。如果使用了被汙染資料,命令注入漏洞就產生了。exec 是用於執行shell命令的函式。它返回執行並返回命令輸出的最後一行,但你可以指定乙個陣列作為第二個引數,這樣輸出的每一行都會作為乙個元素存入陣列。使用方式如下 l...