過濾使用者資料是 Web 應用安全的基礎。它是驗證資料合法性的過程。透過對所有的輸入資料進行過濾,可以避免惡意資料在程式中被誤信或誤用。大多數 Web 應用的漏洞都是因為沒有對使用者輸入的資料進行恰當過濾所引起的。
我們介紹的過濾資料分成三個步驟:
- 1、識別資料,搞清楚需要過濾的資料來自於哪裡
- 2、過濾資料,弄明白我們需要什麼樣的資料
- 3、區分已過濾及被汙染資料,如果存在攻擊資料那麼保證過濾之後可以讓我們使用更安全的資料
“識別資料”作為第一步是因為在你不知道“資料是什麼,它來自於哪裡”的前提下,你也就不能正確地過濾它。這裡的資料是指所有源自非程式碼內部提供的資料。例如 : 所有來自客戶端的資料,但客戶端並不是唯一的外部資料來源,資料庫和第三方提供的介面資料等也可以是外部資料來源。
由使用者輸入的資料我們透過 Go 非常容易識別,Go 透過r.ParseForm
之後,把使用者 POST 和 GET 的資料全部放在了r.Form
裡面。其它的輸入要難識別得多,例如,r.Header
中的很多元素是由客戶端所操縱的。常常很難確認其中的哪些元素組成了輸入,所以,最好的方法是把裡面所有的資料都看成是使用者輸入。(例如r.Header.Get("Accept-Charset")
這樣的也看做是使用者輸入,雖然這些大多數是瀏覽器操縱的)
在知道資料來源之後,就可以過濾它了。過濾是一個有點正式的術語,它在平時表述中有很多同義詞,如驗證、清潔及淨化。儘管這些術語表面意義不同,但它們都是指的同一個處理:防止非法資料進入你的應用。
過濾資料有很多種方法,其中有一些安全性較差。最好的方法是把過濾看成是一個檢查的過程,在你使用資料之前都檢查一下看它們是否是符合合法資料的要求。而且不要試圖好心地去糾正非法資料,而要讓使用者按你制定的規則去輸入資料。歷史證明了試圖糾正非法資料往往會導致安全漏洞。這裡舉個例子:“最近建設銀行系統升級之後,如果密碼後面兩位是 0,只要輸入前面四位就能登入系統”,這是一個非常嚴重的漏洞。
過濾資料主要採用如下一些函式庫來操作:
- strconv 套件下面的字串轉化相關函式,因為從 Request 中的
r.Form
回傳的是字串,而有些時候我們需要將之轉化成整/浮點數,Atoi
、ParseBool
、ParseFloat
、ParseInt
等函式就可以派上用場了。 - string 套件下面的一些過濾函式
Trim
、ToLower
、ToTitle
等函式,能夠幫助我們按照指定的格式取得資訊。 - regexp 套件用來處理一些複雜的需求,例如判定輸入是否是 Email、生日之類別。
過濾資料除了檢查驗證之外,在特殊時候,還可以採用白名單。即假定你正在檢查的資料都是非法的,除非能證明它是合法的。使用這個方法,如果出現錯誤,只會導致把合法的資料當成是非法的,而不會是相反,儘管我們不想犯任何錯誤,但這樣總比把非法資料當成合法資料要安全得多。
如果完成了上面的兩步,資料過濾的工作就基本完成了,但是在編寫 Web 應用的時候我們還需要區分已過濾和被汙染資料,因為這樣可以保證過濾資料的完整性,而不影響輸入的資料。我們約定把所有經過過濾的資料放入一個叫全域性的 Map 變數中(CleanMap)。這時需要用兩個重要的步驟來防止被汙染資料的注入:
- 每個請求都要初始化 CleanMap 為一個空 Map。
- 加入檢查及阻止來自外部資料來源的變數命名為 CleanMap。
接下來,讓我們透過一個例子來鞏固這些概念,請看下面這個表單
<form action="/whoami" method="POST">
我是誰:
<select name="name">
<option value="astaxie">astaxie</option>
<option value="herry">herry</option>
<option value="marry">marry</option>
</select>
<input type="submit" />
</form>
在處理這個表單的程式設計邏輯中,非常容易犯的錯誤是認為只能提交三個選擇中的一個。其實攻擊者可以模擬 POST 操作,提交 name=attack
這樣的資料,所以在此時我們需要做類似白名單的處理
r.ParseForm()
name := r.Form.Get("name")
CleanMap := make(map[string]interface{}, 0)
if name == "astaxie" || name == "herry" || name == "marry" {
CleanMap["name"] = name
}
上面程式碼中我們初始化了一個 CleanMap 的變數,當判斷取得的 name 是astaxie
、herry
、marry
三個中的一個之後
,我們把資料儲存到了 CleanMap 之中,這樣就可以確保 CleanMap["name"]中的資料是合法的,從而在程式碼的其它部分使用它。當然我們還可以在 else 部分增加非法資料的處理,一種可能是再次顯示錶單並提示錯誤。但是不要試圖為了友好而輸出被汙染的資料。
上面的方法對於過濾一組已知的合法值的資料很有效,但是對於過濾有一組已知合法字元組成的資料時就沒有什麼幫助。例如,你可能需要一個使用者名稱只能由字母及數字組成:
r.ParseForm()
username := r.Form.Get("username")
CleanMap := make(map[string]interface{}, 0)
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok {
CleanMap["username"] = username
}
資料過濾在 Web 安全中起到一個基石的作用,大多數的安全問題都是由於沒有過濾資料和驗證資料引起的,例如前面小節的 CSRF 攻擊,以及接下來將要介紹的 XSS 攻擊、SQL 注入等都是沒有認真地過濾資料引起的,因此我們需要特別重視這部分的內容。
- 目錄
- 上一節:預防 CSRF 攻擊
- 下一節:避免 XSS 攻擊