天天看點

前後端分離架構下CSRF防禦機制

前後端分離架構下CSRF防禦機制

  背景

1、什麼是CSRF攻擊?

這裡不再介紹CSRF,已經了解CSRF原理的同學可以直接跳到:“3、前後端分離下有何不同?”。

不太了解的同學可以看這兩篇對CSRF介紹比較詳細的參考文章:

CSRF 攻擊的應對之道

淺談CSRF攻擊方式

如果來不及了解CSRF的原理,可以這麼了解:有一個人發給你一個搞(mei)笑(nv)圖檔連結,你打開這個連結之後,便立刻收到了短信:你的銀行裡的錢已經轉移到這個人的帳戶了。

2、有哪些防禦方案?

上面這個例子當然有點危言聳聽,當然可以确定的是确實會有這樣的漏洞:你打開了一個未知域名的連結,然後你就自動發了條廣告文章、你的Gmail的郵件内容就洩露了、你的百度登入狀态就沒了……

防禦方案在上面的兩篇文章裡也有提到,總結下,無外乎三種:

使用者操作限制,比如驗證碼;

請求來源限制,比如限制HTTP Referer才能完成操作;

token驗證機制,比如請求資料字段中添加一個token,響應請求時校驗其有效性;

第一種方案明顯嚴重影響了使用者體驗,而且還有額外的開發成本;第二種方案成本最低,但是并不能保證100%安全,而且很有可能會埋坑;第三種方案,可取!

token驗證的CSRF防禦機制是公認最合适的方案,也是本文讨論的重點。

3、前後端分離下有何不同?

《CSRF 攻擊的應對之道》這篇文章裡有提到:

要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的

我們前端架構早已經告别了服務端語言(PHP/JAVA等)綁定路由、攜帶資料渲染模闆引擎的方式(畢竟是2011年的文章了,我們笑而不語)。

當然, 前端不要高興的太早:前後端分離之後,Nodejs不具備完善的服務端SESSION、資料庫等功能。

總結一下,在“更先進”的前端架構下,與以往的架構會有一些差別:

Nodejs層不處理SESSION,無法直接實作會話狀态資料儲存;

所有的資料通過Ajax異步擷取,可以靈活實作token方案;

實作思路

如上文提到,這裡僅僅讨論在“更先進”的前端後端架構背景下的token防禦方案的實作。

1、可行性方案

token防禦的整體思路是:

第一步:後端随機産生一個token,把這個token儲存在SESSION狀态中;同時,後端把這個token交給前端頁面;

第二步:下次前端需要發起請求(比如發帖)的時候把這個token加入到請求資料或者頭資訊中,一起傳給後端;

第三步:後端校驗前端請求帶過來的token和SESSION裡的token是否一緻;

上文提到過,前後端分離狀态下,Nodejs是不具備SESSION功能的。那這種token防禦機制是不是就無法實作了呢?

肯定不是。我們可以借助cookie把這個流程更新下:

第一步:後端随機産生一個token,基于這個token通過SHA-56等雜湊演算法生成一個密文;

第二步:後端将這個token和生成的密文都設定為cookie,傳回給前端;

第三步:前端需要發起請求的時候,從cookie中擷取token,把這個token加入到請求資料或者頭資訊中,一起傳給後端;

第四步:後端校驗cookie中的密文,以及前端請求帶過來的token,進行正向散列驗證;

當然這樣實作也有需要注意的:

雜湊演算法都是需要計算的,這裡會有性能風險;

token參數必須由前端處理之後交給後端,而不能直接通過cookie;

cookie更臃腫,會不可避免地讓頭資訊更重;

現在方案确定了,具體該如何實作呢?

2、具體實作

我們的技術棧是 koa(服務端) + Vue.js(前端) 。有興趣可以看這些資料:

趣店前端團隊基于koajs的前後端分離實踐

koa-grace——基于koa的标準前後端分離架構

grace-vue-webpack-boilerplate

在服務端,實作了一個token生成的中間件,koa-grace-csrf:

// 注意:代碼有做精簡

const tokens = require('./lib/tokens');

return function* csrf(next) {

let curSecret = this.cookies.get('密文的cookie');

// 其他如果要擷取參數,則為配置參數值

let curToken = '請求http頭資訊中的token';

// token不存在

if (!curToken || !curSecret) {

return this.throw('CSRF Token Not Found!',403)

}

// token校驗失敗

if (!tokens.verify(curSecret, curToken)) {

return this.throw('CSRF token Invalid!',403)

yield next;

// 無論何種情況都種兩個cookie

// cookie_key: 目前token的cookie_key,httpOnly

let secret = tokens.secretSync();

this.cookies.set(options.cookie_key, secret);

// cookie_token: 目前token的的content,不需要httpOnly

let newToken = tokens.create(secret);

this.cookies.set(options.cookie_token, newToken)

在前端代碼中,對發送ajax請求的封裝稍作優化:

this.$http.post(url, data, {

headers: {

'http請求頭資訊字段名': 'cookie中的token'

}).then((res) => {})

總結一下:

Nodejs生成一個随機數,通過随機數生成散列密文;并将随機數和密文存到cookie;

用戶端JS擷取cookie中的随機數,通過http頭資訊交給Nodejs;

Nodejs響應請求,校驗cookie中的密文和頭資訊中的随機數是否比對;

這裡依舊有個細節值得提一下:Nodejs的上層一般是nginx,而nginx預設會過濾頭資訊中不合法的字段(比如頭資訊字段名包含“_”的),這裡在寫頭資訊的時候需要注意。

上文也提到,通過cookie及http頭資訊傳遞加密token會有很多弊端;有沒有更優雅的實作方案呢?

3、更優雅的架構

首先,我們明确前後端分離的一些基本原則:

後端(Java / PHP )職責:

服務層顆粒化接口,以便前端Nodejs層異步并發調用;

使用者狀态儲存,實作使用者權限等各種功能;

前端(Nodejs + Javascript)職責:

Nodejs層完成路由托管及模闆引擎渲染功能

Nodejs層不負責實作任何SESSION和資料庫功能

我們提到,前端Nodejs層不負責實作任何SESSION和資料庫功能,但有沒有可能把後端緩存系統做成公共服務提供給Nodejs層使用呢?想想感覺前端整條路都亮了有木有?!這裡先挖一個坑,後續慢慢填。

4、延伸

這裡再順便提一下,新架構下的XSS防禦。

猶記得,在狼廠使用PHP的年代,經常被安全部門曝出各類XSS漏洞,然後就在smaty裡添加各種escape濾鏡,但是添加之後發現竟然把原始資料也給轉義了。

當然,現在更多要歸功于各種MVVM單頁面應用:使得前端完全不需要通過讀取URL中的參數來控制VIEW。

不過,還有一點值得一提:前後端分離架構下,路由由Nodejs控制;我自己要擷取的後端參數和需要用在業務邏輯的參數,在主觀上前端同學更好把握一些。

是以, 在koa(服務端) + Vue.js(前端)架構下基本不用顧慮XSS問題(至少不會被全安組追着問XSS漏洞啥時候修複)。

總結

要不學PHP、看Java、玩Python做全棧好了?

本文轉自d1net(轉載)