# 9.2 確保輸入過濾
過濾用戶數據是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注入等都是沒有認真地過濾數據引起的,因此我們需要特別重視這部分的內容。
## links
* [目錄](<preface.md>)
* 上一節: [預防CSRF攻擊](<09.1.md>)
* 下一節: [避免XSS攻擊](<09.3.md>)
- 目錄
- Go環境配置
- Go安裝
- GOPATH 與工作空間
- Go 命令
- Go開發工具
- 小結
- Go語言基礎
- 你好,Go
- Go基礎
- 流程和函數
- struct
- 面向對象
- interface
- 并發
- 小結
- Web基礎
- web工作方式
- Go搭建一個簡單的web服務
- Go如何使得web工作
- Go的http包詳解
- 小結
- 表單
- 處理表單的輸入
- 驗證表單的輸入
- 預防跨站腳本
- 防止多次遞交表單
- 處理文件上傳
- 小結
- 訪問數據庫
- database/sql接口
- 使用MySQL數據庫
- 使用SQLite數據庫
- 使用PostgreSQL數據庫
- 使用beedb庫進行ORM開發
- NOSQL數據庫操作
- 小結
- session和數據存儲
- session和cookie
- Go如何使用session
- session存儲
- 預防session劫持
- 小結
- 文本文件處理
- XML處理
- JSON處理
- 正則處理
- 模板處理
- 文件操作
- 字符串處理
- 小結
- Web服務
- Socket編程
- WebSocket
- REST
- RPC
- 小結
- 安全與加密
- 預防CSRF攻擊
- 確保輸入過濾
- 避免XSS攻擊
- 避免SQL注入
- 存儲密碼
- 加密和解密數據
- 小結
- 國際化和本地化
- 設置默認地區
- 本地化資源
- 國際化站點
- 小結
- 錯誤處理,調試和測試
- 錯誤處理
- 使用GDB調試
- Go怎么寫測試用例
- 小結
- 部署與維護
- 應用日志
- 網站錯誤處理
- 應用部署
- 備份和恢復
- 小結
- 如何設計一個Web框架
- 項目規劃
- 自定義路由器設計
- controller設計
- 日志和配置設計
- 實現博客的增刪改
- 小結
- 擴展Web框架
- 靜態文件支持
- Session支持
- 表單支持
- 用戶認證
- 多語言支持
- pprof支持
- 小結
- 參考資料