國際慣例,先來一波名詞解釋(來自維基百科):
CSRF: 跨站請求偽造(corss-site request forgery) 也被稱為 one-click 或 session riding,也可稱為 XSRF。是一種挾持用戶在當(dāng)前已登錄的 web 應(yīng)用程序上執(zhí)行非本意的操作的攻擊方法。跟 XSS(跨站腳本攻擊) 相比,XSS 利用的是用戶對指定網(wǎng)站的信任,而 CSRF 利用的是網(wǎng)站對用戶瀏覽器的信任。
先來談?wù)勎覟槭裁匆獙戇@篇文章。主要原因有二:
? 1)? 內(nèi)容過舊;
? 2)? 關(guān)鍵點(diǎn)講的不夠詳細(xì),比如:禁用 CORS 可以防范 CSRF。初看有一種一臉懵逼的感覺(也有可能我太菜雞???);
另外,起初了解時,跟朋友聊到這個話題,發(fā)現(xiàn)朋友對 CSRF 存在很大的誤解:

這里強(qiáng)調(diào)一下:CSRF 的目的并非竊取用戶的身份信息!因?yàn)?CSRF 攻擊者根本拿不到受害者的任何信息。
接下來切入正題。
如何確認(rèn)應(yīng)用是否存在 CSRF 漏洞?
在談如何確認(rèn)是否存在該漏洞之前,我們先來了解一次成功的 CSRF 攻擊成功所必要的前提條件:
????1)? 已在攻擊目標(biāo)站點(diǎn)登錄授權(quán),且應(yīng)用中使用了 form 表單發(fā)起會造成數(shù)據(jù)更改的請求;
????2)? 不規(guī)范的使用 HTTP 方法,如何定義不規(guī)范使用? 如:使用 get 請求修改數(shù)據(jù);
當(dāng)然, 還依賴你的登錄環(huán)境以及你沒有做 CSRF 防御這個前提。
有了這兩個前提條件,那我們?nèi)ザㄎ豢赡艽嬖趩栴}的代碼就簡單很多了。我們只需要應(yīng)用中是否存在 form 提交數(shù)據(jù)(注意,是提交數(shù)據(jù),不是加載數(shù)據(jù)。即該類請求會造成數(shù)據(jù)庫數(shù)據(jù)的的更改);再者,檢查應(yīng)用中是否有 GET、HEAD 請求造成數(shù)據(jù)庫數(shù)據(jù)更改的接口。?
如何防御 CSRF?
先來講幾個網(wǎng)上普遍說的幾種方案:
1. 驗(yàn)證 HTTP Referer
????這算是最簡單快速的防范 CSRF 的方法,原理很簡單:
即在處理請求之前,檢查 referer 值是否站點(diǎn)本身的 hosts, 如果不是,則拒絕此次請求。你可能會說在一些低版本的瀏覽器中是存在可以篡改 referer 的方法的。 在這里我并不打算談 IE6、firefox2 存在篡改 referer 方法, 畢竟這些都是過去式, 在當(dāng)今的 web 環(huán)境下,根本不需要考慮這個問題。那是不是可以認(rèn)為這是個既簡單又完美的方案呢?答案是否定的:
????1)? 在 firefox 中,可以設(shè)置禁用 referer,具體原因與操作方法如果你愿意了解,可以點(diǎn)我
????2)? 嗯...并且 Opera 也有這個功能, 操作方法點(diǎn)我,并搜索文本 “Referrer logging” 檢索到內(nèi)容
????3) chrome 也允許以修改啟動項(xiàng)參數(shù)的方式禁用 referer,當(dāng)然,這么干的人也不是什么電腦文盲。具體操作方式點(diǎn)我
看到上面的 1 2 3,你還敢使用這 簡單、“完美”作為你的應(yīng)用的防范 CSRF 的方案嗎?反正我不敢。
2. CSRF token
這種防范機(jī)制, 結(jié)合我自己的理解, 大概可以描述為(本想畫成圖的,無奈作圖水平太差,只好以文字表達(dá)):
每次頁面渲染時,往頁面某個元素上寫入 token, 比如 <input id="J_csrfToken" type="hidden"? value="{{csrfToken}}" />,并為頁面上所有的 a.href都加上 csrfToken 的 qs 參數(shù),而對于 form, 則在表單域內(nèi)創(chuàng)建 <input type="hidden" name="csrf-token" value="{{csrfToken}}" />??,如此一來,當(dāng)出發(fā) a.href 或 form.submit 時, csrfToken 都以 qs 的方式發(fā)送給服務(wù)端, 服務(wù)端根據(jù)該 token 進(jìn)行校驗(yàn)確定是否為合法請求。 而對于動態(tài)生成的 html 片段, 則通過 document.getElementById('J_csrfToken').value 的方式手動編碼傳輸給服務(wù)端。
那我們何時需要采用這種方法來防御 CSRF?你只要需要確認(rèn):
? ? 1)? 是否采用有副作用的 GET、HEAD 請求
? ? 2)? 是否有采用 form.submit 的方式修改數(shù)據(jù)庫數(shù)據(jù)
如果有的話,那你可以考慮采用這種方式來防御
3. 禁用 CORS
實(shí)驗(yàn)證明,簡單的禁用 CORS 并不能有效防御 CSRF,它需要一些前提:
? ? 1)正確的使用了 HTTP 方法, 不要使 GET、 HEAD 有副作用;
? ? 2)如果你使用了有副作用的 GET, 攻擊者仍然可以通過創(chuàng)建 script 標(biāo)簽再指定 src 為對應(yīng)的請求即可完成攻擊;
? ? 3)如果你使用了有副作用的 GET且沒有做登錄驗(yàn)證,那么使用 xhr 也能輕松完成攻擊,畢竟 CORS 屬于瀏覽器的安全策略, 它只攔截響應(yīng)而非請求;
其他的一些文章中, 也有講到一些其他的方案, 但我覺得沒有實(shí)踐價值,就沒必要贅述(其實(shí)我覺得這里列出來的,也沒啥實(shí)踐價值。只是整體消化下來,感覺總有一些點(diǎn)沒講清楚,就作為補(bǔ)充來描述一下)。接下來談?wù)剛€人理解的防范之道:
1. 正確使用 HTTP 方法;
2. 不要使 GET、HEAD 有副作用;
3. 存在 form 表單提交數(shù)據(jù)的場景時,可以使用 csrfToken 方案;
做到以上幾點(diǎn), 我相信基本可以將 CSRF 拒之門外。
如何發(fā)起一次 CSRF 攻擊?
根據(jù)一次成功的 CSRF 攻擊所需要的必備條件, 首先當(dāng)然是需要攻擊目標(biāo)在站點(diǎn)登錄。
ok, 接下來腦補(bǔ)一下:作為攻擊者的你,做了一個精心準(zhǔn)備的算命網(wǎng)站,上面一個醒目大按鈕——“測一測”,點(diǎn)擊時,其實(shí)是發(fā)起了一個 form.submit ,將必要的字段都放置其中,坐等受害者上鉤。一旦用戶點(diǎn)擊, 你將以受害者的名義以你想要的結(jié)果修改了受害者的數(shù)據(jù),達(dá)成攻擊目標(biāo)。
以上,希望對你有幫助!另,也歡迎勘誤!