維基百科上有一篇「Secure input and output handling」說明要怎麼處理 input 與 output (對資安方面的說明)。標題的 Filter Input 與 Escape Output 是 Gasol 之前提到後才知道的名詞,以前只知道要 validate & escape,沒想過一個比較好記的念法...
這邊都以 PHP 為主,其他程式語言也應該會有對應的方式...
Input 有很多管道,有可能是使用者或是 3rd party 廠商透過 Form 傳進來的資料 (在 PHP 裡可能是 $_GET
或是 $_POST
),也有可能是 cookie 的資料 (因為使用者可以修改,所以視為不安全的資料)。從檔案讀資料進來 (可能是普通的文字檔,或是 XML,也可能是圖片) 也算是 input。
Output 也有很多管道,像是 HTML、JSON,或是組 SQL statement 時使用變數。
Filter Input
在處理 input data 時,一般常常忘記的是「先強制轉成 Non-null UTF-8 string」(現在一般都是用 UTF-8,所以這邊就只講 UTF-8)。
這是因為很多 PHP function 對非 Non-null UTF-8 string 有非定義行為 (undefined behavior,不保證效果與輸出結果的正確性),加上一般常見的產品需求可以用 Non-null UTF-8 string 滿足,所以在 PHP 內拿到資料時可以先 filter 過。
Update:下面說的步驟錯了,請參考「關於 Non-null string 的處理...」這篇的說明。
有兩個步驟要做,第一個是確保他是 Non-null,直接把 \0
以及以後的東西幹掉:
$str_out = preg_replace('/\0.*/g', '', $str_in);
$str = preg_replace('/\0.*/g', '', $str); // 直接取代原來字串
第二個是確保他是 UTF-8 string。這點可以直接用 iconv()
轉,而不用自己寫 regex 處理了:
$str_out = iconv('UTF-8', 'UTF-8', $str_in);
$str = iconv('UTF-8', 'UTF-8', $str);
用 iconv()
從 UTF-8 轉到 UTF-8 是一個特別的用法,我是在 Cal Henderson 的「Building Scalable Web Sites: Building, Scaling, and Optimizing the Next Generation of Web Applications」上看到的 (有中譯版)。
如果傳進來的值本來就假設是整數,那麼就用 intval()
轉一次。如果假設是非負整數 (包含 0),那麼就用 abs()
與 intval()
。如果是文字類的 (像是 e-mail),可以再用其他的 regex 檢查。
一般來說,白名單會比黑名單好。不過這也是一般性,很多時候還是很囧的...
Escape Output
Escape 指的是 Escape character (轉義字元)。不同的情況下會有不同的 escape character,所以保護的方式也不一樣。
以 HTML 來說,想要顯示小於符號 <
,實際上要用 <
表示。而在 PHP 裡面常用的 htmlspecialchars()
定義了「只 escape 五個符號」,剛好可以拿來用:
<div><?= htmlspecialchars($str) ?></div>
也有人推薦 htmlentities()
,不過因為轉的比較多 (所以要考慮的行為比較多),我比較不喜歡...
對於 XML,在 XML 的規範裡規定一定要把 <
與 &
換成 <
與 &
(Character Data and Markup),但 >
則可以 (非必要) 換成 >
。也允許把雙引號 "
換成 ",以及單引號 '
換成 '
。
所以 XML 剛好可以直接用 htmlspecialchars()
(確認完全符合 spec 要求),反過來 htmlentities()
就不保證了。
再來是 MySQL 的 escape 與 charset 有關,所以要用 mysql_real_escape_string()
或是 PDO::quote()
,但更好的方法應該是使用 prepare & execute (binding variables)...
而 shell 的 escape 則應該用 escapeshellarg()
再帶入 string 裡。
每一種 output 所需要的 escape function 不同,都不能直接拿 addslashes()
來用... (這個 function 的設計就很 PHP...)
純粹 JSON 的話,用 json_encode()
就可以幫你處理好。問題是在 HTML + JSON 時會讓你處理到抱頭痛哭... 這就是另外的故事了 +_+
結論?
Filter Input + Escape Output 只是最基本的一步,不過在大多數的狀況下,這個方法就可以擋下不少問題了,算是很實用的 policy...