{% raw %}
# 9.1 預防CSRF攻擊
## 什么是CSRF
CSRF(Cross-site request forgery),中文名稱:跨站請求偽造,也被稱為:one click attack/session riding,縮寫為:CSRF/XSRF。
那么CSRF到底能夠干嘛呢?你可以這樣簡單的理解:攻擊者可以盜用你的登陸信息,以你的身份模擬發送各種請求。攻擊者只要借助少許的社會工程學的詭計,例如通過QQ等聊天軟件發送的鏈接(有些還偽裝成短域名,用戶無法分辨),攻擊者就能迫使Web應用的用戶去執行攻擊者預設的操作。例如,當用戶登錄網絡銀行去查看其存款余額,在他沒有退出時,就點擊了一個QQ好友發來的鏈接,那么該用戶銀行帳戶中的資金就有可能被轉移到攻擊者指定的帳戶中。
所以遇到CSRF攻擊時,將對終端用戶的數據和操作指令構成嚴重的威脅;當受攻擊的終端用戶具有管理員帳戶的時候,CSRF攻擊將危及整個Web應用程序。
## CSRF的原理
下圖簡單闡述了CSRF攻擊的思想

圖9.1 CSRF的攻擊過程
從上圖可以看出,要完成一次CSRF攻擊,受害者必須依次完成兩個步驟 :
- 1.登錄受信任網站A,并在本地生成Cookie 。
- 2.在不退出A的情況下,訪問危險網站B。
看到這里,讀者也許會問:“如果我不滿足以上兩個條件中的任意一個,就不會受到CSRF的攻擊”。是的,確實如此,但你不能保證以下情況不會發生:
- 你不能保證你登錄了一個網站后,不再打開一個tab頁面并訪問另外的網站,特別現在瀏覽器都是支持多tab的。
- 你不能保證你關閉瀏覽器了后,你本地的Cookie立刻過期,你上次的會話已經結束。
- 上圖中所謂的攻擊網站,可能是一個存在其他漏洞的可信任的經常被人訪問的網站。
因此對于用戶來說很難避免在登陸一個網站之后不點擊一些鏈接進行其他操作,所以隨時可能成為CSRF的受害者。
CSRF攻擊主要是因為Web的隱式身份驗證機制,Web的身份驗證機制雖然可以保證一個請求是來自于某個用戶的瀏覽器,但卻無法保證該請求是用戶批準發送的。
## 如何預防CSRF
過上面的介紹,讀者是否覺得這種攻擊很恐怖,意識到恐怖是個好事情,這樣會促使你接著往下看如何改進和防止類似的漏洞出現。
CSRF的防御可以從服務端和客戶端兩方面著手,防御效果是從服務端著手效果比較好,現在一般的CSRF防御也都在服務端進行。
服務端的預防CSRF攻擊的方式方法有多種,但思想上都是差不多的,主要從以下2個方面入手:
- 1、正確使用GET,POST和Cookie;
- 2、在非GET請求中增加偽隨機數;
我們上一章介紹過REST方式的Web應用,一般而言,普通的Web應用都是以GET、POST為主,還有一種請求是Cookie方式。我們一般都是按照如下方式設計應用:
1、GET常用在查看,列舉,展示等不需要改變資源屬性的時候;
2、POST常用在下達訂單,改變一個資源的屬性或者做其他一些事情;
接下來我就以Go語言來舉例說明,如何限制對資源的訪問方法:
mux.Get("/user/:uid", getuser)
mux.Post("/user/:uid", modifyuser)
這樣處理后,因為我們限定了修改只能使用POST,當GET方式請求時就拒絕響應,所以上面圖示中GET方式的CSRF攻擊就可以防止了,但這樣就能全部解決問題了嗎?當然不是,因為POST也是可以模擬的。
因此我們需要實施第二步,在非GET方式的請求中增加隨機數,這個大概有三種方式來進行:
- 為每個用戶生成一個唯一的cookie token,所有表單都包含同一個偽隨機值,這種方案最簡單,因為攻擊者不能獲得第三方的Cookie(理論上),所以表單中的數據也就構造失敗,但是由于用戶的Cookie很容易由于網站的XSS漏洞而被盜取,所以這個方案必須要在沒有XSS的情況下才安全。
- 每個請求使用驗證碼,這個方案是完美的,因為要多次輸入驗證碼,所以用戶友好性很差,所以不適合實際運用。
- 不同的表單包含一個不同的偽隨機值,我們在4.4小節介紹“如何防止表單多次遞交”時介紹過此方案,復用相關代碼,實現如下:
生成隨機數token
h := md5.New()
io.WriteString(h, strconv.FormatInt(crutime, 10))
io.WriteString(h, "ganraomaxxxxxxxxx")
token := fmt.Sprintf("%x", h.Sum(nil))
t, _ := template.ParseFiles("login.gtpl")
t.Execute(w, token)
輸出token
<input type="hidden" name="token" value="{{.}}">
驗證token
r.ParseForm()
token := r.Form.Get("token")
if token != "" {
//驗證token的合法性
} else {
//不存在token報錯
}
這樣基本就實現了安全的POST,但是也許你會說如果破解了token的算法呢,按照理論上是,但是實際上破解是基本不可能的,因為有人曾計算過,暴力破解該串大概需要2的11次方時間。
## 總結
跨站請求偽造,即CSRF,是一種非常危險的Web安全威脅,它被Web安全界稱為“沉睡的巨人”,其威脅程度由此“美譽”便可見一斑。本小節不僅對跨站請求偽造本身進行了簡單介紹,還詳細說明造成這種漏洞的原因所在,然后以此提了一些防范該攻擊的建議,希望對讀者編寫安全的Web應用能夠有所啟發。
## links
* [目錄](<preface.md>)
* 上一節: [安全與加密](<09.0.md>)
* 下一節: [確保輸入過濾](<09.2.md>)
{% endraw %}
- 目錄
- 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支持
- 小結
- 參考資料