前後端分離下的 CSRF/XSRF

前言

CSRF 雖然滿常見的,大多時候都可以解決,但是在完全前後分離下的處理方式又是另一回事。

CSRF

由於先前的文章 全端勇士之路 Node.js 基礎學習-CSRF 篇 已經有描述相關的例子,所以這邊就直接貼上先前的描述

CSRF 簡單來講就是利用使用者瀏覽器對於網站的信任來達到攻擊,假設我們已經知道某某某 API 路徑,例如刪除帳號的API https:/www.example.com/delete?id=XXX,然後黑客就傳給你一個句話「快看!點這個網址可以取得一個比特幣」(屁)

因此其實在 維基百科 上也有特別粗體黑字標示 CSRF 的重點觀念

這利用了web中使用者身分驗證的一個漏洞:簡單的身分驗證只能保證請求發自某個使用者的瀏覽器,卻不能保證請求本身是使用者自願發出的。

在前面的文章中,就有描述到在沒有前後端分離的狀況下是如何處理 CSRF 的,基本上簡單來講就是透過 SSR 渲染畫面時,同時後端往前端丟一個 CSRF Token 的隨機字串,並儲存在畫面某個地方(通常是隱藏表單),所以當你打開開發者工具時,是可以找到一個畫面看不到的隱藏 input 欄位的:

1
<input type="hidden" name="_csrf" value="BUMy6bl3-wtVBYgP9zA8xZLtFEP68mqNbQ-w">

JavaScript 直播班 API

透過這種方式後端則必須暫存一個 CSRF Token,當前端往後端送資料時,就會取出該欄位來做比對,值若相同時才會繼續往下動作(新增、編輯等行為)。

在上面所講的主要都是在「沒有完整的前後端分離」狀況下實作,這一段可能沒有什麼太大問題,但是在完整的前後端分離的狀況,那麼狀況就會完全不同,畢竟我們已經脫離了後端環境,因此前端的畫面就不是透過後端來渲染,那這樣子我們該如何取得 CSRF Token 呢?所以這邊這句話就是重點了

「透過瀏覽器來生成 CSRF Token」

這時候你可能會很疑惑,難道有 CORS 沒用嗎?或者開一個後端 API 來取得 CSRF Token 不就好了嗎?

可以試著思考一下,網路上的 CORS 都有可能被繞過的,畢竟 CORS 最多只能防禦 XMLHttpRequest,而若特別開一個 API 來取得 CSRF Token 也相當奇怪,不就代表著我如果可以繞過 CORS 那麼不就可以一直請求拿到 CSRF Token?

那麼回歸正題講講 Client 的 Double Submit Cookie,前面有講到會「透過瀏覽器來生成 CSRF Token」,那麼這一段可以怎麼做呢?舉例來講我們可以寫一個隨機字串產生器

1
2
3
4
5
6
7
8
9
10
11
function  generateRandomString (num) {
const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result1= ' ';
const charactersLength = characters.length;
for ( let i = 0; i < num; i++ ) {
result1 += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result1;
}

document.cookie = `XSRF-TOKEN=${generateRandomString}`;

那麼這邊 XSRF-TOKEN 其實就是 CSRF-TOKEN,可以看到 微軟官方 描述也是相同的:

跨網站要求偽造 (也稱為 XSRF 或 CSRF) 是針對 web 裝載的應用程式進行攻擊

接下來再請求往後端打的時候將存在 Cookie 的 XSRF-TOKEN 一起往後端送,如果你使用的是 axios 套件的話,只需要這樣子設定即可(不得不說 axios 超方便):

1
2
axios.defaults.xsrfCookieName: 'XSRF-TOKEN', // 名稱請與儲存在 cookies 相同,axios 預設是 'XSRF-TOKEN'
axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN'; // 這一個主要是請求時,會帶在 header 的名稱,建議與後端溝通,有些後端接收是接收 XSRF-TOKEN,axios 預設是 'X-XSRF-TOKEN'

如果你是在 Vue Cli 環境的話則是以下:

1
2
this.$http.defaults.xsrfCookieName: 'XSRF-TOKEN',
this.$http.defaults.xsrfHeaderName = 'XSRF-TOKEN';

透過上面設置之後,每一次請求 axios 到後端時就會預設帶上由 Client 生成的 CSRF Token,而這個方式也可以避免 CSRF 攻擊問題。

那麼你可能會疑惑為什麼這樣子可以預防 CSRF 攻擊,你可以試想一下,當使用者點了這個網址後的狀況會是如何

1
<a href="https:/www.example.com/delete?id=1">取得比特幣</a>

會因為使用者是直接點連結進入到該頁面,因此並不會有 axios 行為所以就不會有 Client 產生得 XSRF-TOKEN,這樣子後端就不會執行這個刪除行為,因此如果要達到正確的刪除行為你就必須需持有 Token 之外也必須持有 XSRF-TOKEN,這樣子就可以避免這種狀況。

但你可能也會疑惑「可是通常登入的功能必須有 Token 才能夠刪除啊?」

基本上是的,但是如果你傳送時如果能夠多這個保護,是可以減少被攻擊的機會,而這個 CSRF 大部分都是在預防不需要 Token 的頁面為主哩。

參考文獻

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ