閱讀226 返回首頁    go 中電雲集


CSRF冒充用戶之手

起初我一直弄不清楚CSRF 究竟和XSS 有什麼區別,後來才明白CSRF 和XSS 根本是兩個不同維度上的分類。XSS 是實現CSRF 的諸多途徑中的一條,但絕對不是唯一的一條。一般習慣上把通過XSS 來實現的CSRF 稱為XSRF。

CSRF 的全稱是“跨站請求偽造”,而XSS 的全稱是“跨站腳本”。看起來有點相似,它們都是屬於跨站攻擊——不攻擊服務器端而攻擊正常訪問網站的用戶,但前麵說了,它們的攻擊類型是不同維度上的分類。CSRF 顧名思義,是偽造請求,冒充用戶在站內的正常操作。我們知道,絕大多數網站是通過cookie 等方式辨識用戶身份(包括使用服務器端Session 的網站,因為Session ID 也是大多保存在cookie 裏麵的),再予以授權的。所以要偽造用戶的正常操作,最好的方法是通過XSS 或鏈接欺騙等途徑,讓用戶在本機(即擁有身份cookie 的瀏覽器端)發起用戶所不知道的請求。

嚴格意義上來說,CSRF 不能分類為注入攻擊,因為CSRF 的實現途徑遠遠不止XSS 注入這一條。通過XSS 來實現CSRF 易如反掌,但對於設計不佳的網站,一條正常的鏈接都能造成CSRF。

例如,一論壇網站的發貼是通過GET 請求訪問,點擊發貼之後JS 把發貼內容拚接成目標URL 並訪問:

https://www.yunsec.net/bbs/create_post.php?title=標題&content=內容

那麼,我隻需要在論壇中發一帖,包含一鏈接

https://www.yunsec.net/bbs/create_post.php?title=我是腦殘&content=哈哈

隻要有用戶點擊了這個鏈接,那麼他們的帳戶就會在不知情的情況下發布了這一帖子。可能這隻是個惡作劇,但是既然發貼的請求可以偽造,那麼刪帖、轉帳、改密碼、發郵件全都可以偽造。

如何解決這個問題,我們是否可以效仿上文應對XSS 的做法呢?過濾用戶輸入, 不允許發布這種含有站內操作URL 的鏈接。這麼做可能會有點用,但阻擋不了CSRF,因為攻擊者可以通過QQ 或其他網站把這個鏈接發布上去,為了偽裝可能還使用bit.ly 壓縮一下網址,這樣點擊到這個鏈接的用戶還是一樣會中招。所以對待CSRF ,我們的視角需要和對待XSS 有所區別。CSRF 並不一定要有站內的輸入,因為它並不屬於注入攻擊,而是請求偽造。被偽造的請求可以是任何來源,而非一定是站內。所以我們唯有一條路可行,就是過濾請求的處理者。

比較頭痛的是,因為請求可以從任何一方發起,而發起請求的方式多種多樣,可以通過iframe、ajax(這個不能跨域,得先XSS)、Flash 內部發起請求(總是個大隱患)。由於幾乎沒有徹底杜絕CSRF 的方式,我們一般的做法,是以各種方式提高攻擊的門檻。

首先可以提高的一個門檻,就是改良站內API 的設計。對於發布帖子這一類創建資源的操作,應該隻接受POST 請求,而GET 請求應該隻瀏覽而不改變服務器端資源。當然,最理想的做法是使用REST 風格的API 設計,GET、POST、PUT、DELETE 四種請求方法對應資源的讀取、創建、修改、刪除。現在的瀏覽器基本不支持在表單中使用PUT 和DELETE 請求方法,我們可以使用ajax 提交請求(例如通過jquery-form 插件,我最喜歡的做法),也可以使用隱藏域指定請求方法,然後用POST 模擬PUT 和DELETE (Ruby on Rails 的做法)。這麼一來,不同的資源操作區分的非常清楚,我們把問題域縮小到了非GET 類型的請求上——攻擊者已經不可能通過發布鏈接來偽造請求了,但他們仍可以發布表單,或者在其他站點上使用我們肉眼不可見的表單,在後台用js 操作,偽造請求。

接下來我們就可以用比較簡單也比較有效的方法來防禦CSRF,這個方法就是“請求令牌”。讀過《J2EE 核心模式》的同學應該對“同步令牌”應該不會陌生,“請求令牌”和“同步令牌”原理是一樣的,隻不過目的不同,後者是為了解決POST 請求重複提交問題,前者是為了保證收到的請求一定來自預期的頁麵。實現方法非常簡單,首先服務器端要以某種策略生成隨機字符串,作為令牌(token),保存在Session 裏。然後在發出請求的頁麵,把該令牌以隱藏域一類的形式,與其他信息一並發出。在接收請求的頁麵,把接收到的信息中的令牌與Session 中的令牌比較,隻有一致的時候才處理請求,否則返回HTTP 403 拒絕請求或者要求用戶重新登陸驗證身份。

請求令牌雖然使用起來簡單,但並非不可破解,使用不當會增加安全隱患。使用請求令牌來防止CSRF 有以下幾點要注意:

雖然請求令牌原理和驗證碼有相似之處,但不應該像驗證碼一樣,全局使用一個Session Key。因為請求令牌的方法在理論上是可破解的,破解方式是解析來源頁麵的文本,獲取令牌內容。如果全局使用一個Session Key,那麼危險係數會上升。原則上來說,每個頁麵的請求令牌都應該放在獨立的Session Key 中。我們在設計服務器端的時候,可以稍加封裝,編寫一個令牌工具包,將頁麵的標識作為Session 中保存令牌的鍵。

在ajax 技術應用較多的場合,因為很有請求是JavaScript 發起的,使用靜態的模版輸出令牌值或多或少有些不方便。但無論如何,請不要提供直接獲取令牌值的API。這麼做無疑是鎖上了大門,卻又把鑰匙放在門口,讓我們的請求令牌退化為同步令牌。

第一點說了請求令牌理論上是可破解的,所以非常重要的場合,應該考慮使用驗證碼(令牌的一種升級,目前來看破解難度極大),或者要求用戶再次輸入密碼(亞馬遜、淘寶的做法)。但這兩種方式用戶體驗都不好,所以需要產品開發者權衡。

無論是普通的請求令牌還是驗證碼,服務器端驗證過一定記得銷毀。忘記銷毀用過的令牌是個很低級但是殺傷力很大的錯誤。我們學校的選課係統就有這個問題,驗證碼用完並未銷毀,故隻要獲取一次驗證碼圖片,其中的驗證碼可以在多次請求中使用(隻要不再次刷新驗證碼圖片),一直用到Session 超時。這也是為何選課係統加了驗證碼,外掛軟件升級一次之後仍然暢通無阻。

如下也列出一些據說能有效防範CSRF,其實效果甚微的方式甚至無效的做法。

通過referer 判定來源頁麵:referer 是在HTTP Request Head 裏麵的,也就是由請求的發送者決定的。如果我喜歡,可以給referer 任何值。當然這個做法並不是毫無作用,起碼可以防小白。但我覺得性價比不如令牌。

過濾所有用戶發布的鏈接:這個是最無效的做法,因為首先攻擊者不一定要從站內發起請求(上麵提到過了),而且就算從站內發起請求,途徑也遠遠不知鏈接一條。比如<img src=”./create_post.php” /> 就是個不錯的選擇,還不需要用戶去點擊,隻要用戶的瀏覽器會自動加載圖片,就會自動發起請求。

在請求發起頁麵用alert 彈窗提醒用戶:這個方法看上去能幹擾站外通過iframe 發起的CSRF,但攻擊者也可以考慮用window.alert = function(){}; 把alert 弄啞,或者幹脆脫離iframe,使用Flash 來達到目的。

總體來說,目前防禦CSRF 的諸多方法還沒幾個能徹底無解的。所以CSDN 上看到討論CSRF 的文章,一般都會含有“無恥”二字來形容(另一位有該名號的貌似是DDOS 攻擊)。作為開發者,我們能做的就是盡量提高破解難度。當破解難度達到一定程度,網站就逼近於絕對安全的位置了(雖然不能到達)。上述請求令牌方法,就我認為是最有可擴展性的,因為其原理和CSRF 原理是相克的。CSRF 難以防禦之處就在於對服務器端來說,偽造的請求和正常的請求本質上是一致的。而請求令牌的方法,則是揪出這種請求上的唯一區別——來源頁麵不同。我們還可以做進一步的工作,例如讓頁麵中token 的key 動態化,進一步提高攻擊者的門檻。

摘自中國雲安網(www.yunsec.net) 原文:https://www.yunsec.net/a/security/web/jbst/2011/1125/9611_2.html

最後更新:2017-01-04 22:34:54

  上一篇:go 無法連接Windows遠程服務器的幾種解決辦法
  下一篇:go MYSQL GBK轉UTF8(轉)