先後端分離架構下CSRF防護機制

原文: http://feclub.cn/post/content...html

背景

一、什麼是CSRF攻擊?

這裏再也不介紹CSRF,已經瞭解CSRF原理的同窗能夠直接跳到:「三、先後端分離下有何不一樣?」。前端

不太瞭解的同窗能夠看這兩篇對CSRF介紹比較詳細的參考文章:vue

若是來不及瞭解CSRF的原理,能夠這麼理解:有一我的發給你一個搞(mei)笑(nv)圖片連接,你打開這個連接以後,便馬上收到了短信:你的銀行裏的錢已經轉移到這我的的賬戶了git

二、有哪些防護方案?

上面這個例子固然有點危言聳聽,固然能夠肯定的是確實會有這樣的漏洞:你打開了一個未知域名的連接,而後你就自動發了條廣告帖子、你的Gmail的郵件內容就泄露了、你的百度登陸狀態就沒了……github

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

  1. 用戶操做限制,好比驗證碼;ajax

  2. 請求來源限制,好比限制HTTP Referer才能完成操做;算法

  3. token驗證機制,好比請求數據字段中添加一個token,響應請求時校驗其有效性;

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

token驗證的CSRF防護機制是公認最合適的方案,也是本文討論的重點。

三、先後端分離下有何不一樣?

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

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

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

固然, 前端不要高興的太早:先後端分離以後,Nodejs不具有完善的服務端SESSION、數據庫等功能。

總結一下,在「更先進」的前端架構下,與以往的架構會有一些區別:

  • Nodejs層不處理SESSION,沒法直接實現會話狀態數據保存;

  • 全部的數據經過Ajax異步獲取,能夠靈活實現token方案;

實現思路

如上文提到,這裏僅僅討論在「更先進」的前端後端架構背景下的token防護方案的實現。

一、可行性方案

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更臃腫,會不可避免地讓頭信息更重;

如今方案肯定了,具體該如何實現呢?

二、具體實現

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

在服務端,實現了一個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默認會過濾頭信息中不合法的字段(好比頭信息字段名包含「_」的),這裏在寫頭信息的時候須要注意。

"One more thing..."

上文也提到,經過cookie及http頭信息傳遞加密token會有不少弊端;有沒有更優雅的實現方案呢?

一、cookie中samesite屬性

回溯下CSRF產生的根本緣由:cookie會被第三方發起的跨站請求攜帶,這本質上是HTTP協議設計的漏洞。

那麼,咱們能不能經過cookie的某個屬性禁止cookie的這個特性呢?

好消息是,在最新的RFC規範中已經加入了「samesite」屬性。細節這裏再也不贅述,能夠參考:

  1. SameSite Cookie,防止 CSRF 攻擊

  2. Same-site Cookies

二、更優雅的架構

固然,目前爲止,客戶端對samesite屬性的支持並非特別好;回到先後端分離架構下,咱們明確下先後端分離框架的基本原則:

後端(Java / PHP )職責:

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

  • 用戶狀態保存,實現用戶權限等各類功能;

前端(Nodejs + Javascript)職責:

  • Nodejs層完成路由託管及模板引擎渲染功能

  • Nodejs層不負責實現任何SESSION和數據庫功能

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

三、延伸

這裏再順便提一下,新架構下的XSS防護

猶記得,在狼廠使用PHP的年代,常常被安所有門曝出各種XSS漏洞,而後就在smaty裏添加各類escape濾鏡,可是添加以後發現居然把原始數據也給轉義了。

固然,如今更多要歸功於各類MVVM單頁面應用:使得前端徹底不須要經過讀取URL中的參數來控制VIEW。

不過,還有一點值得一提:先後端分離框架下,路由由Nodejs控制;我本身要獲取的後端參數和須要用在業務邏輯的參數,在主觀上前端同窗更好把握一些。

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

總結

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

相關文章
相關標籤/搜索