跨站請求偽造 (Cross-site request forgery,CSRF) 是一種攻擊類型,它強迫最終使用者在他/她目前已通過身份驗證的 Web 應用程式後端上執行非預期的動作。換句話說,若沒有保護機制,儲存在瀏覽器 (如 Google Chrome) 中的 Cookie 可以被用來從使用者電腦發送請求到 Chase.com,無論該使用者目前正在瀏覽 Chase.com 或是 Horrible-Hacker-Site.com。
CSRF 令牌就像限量版贈品。雖然 Session 告訴伺服器使用者「證明了他們的身分」,CSRF 令牌則告訴伺服器他們「來自他們聲稱的地方」。當您的 Sails 應用程式啟用 CSRF 保護時,所有對伺服器的非 GET 請求都必須附帶一個特殊的「CSRF 令牌」,可以作為「_csrf」參數或「X-CSRF-Token」標頭包含在內。
使用令牌保護您的 Sails 應用程式免於跨站請求偽造 (或 CSRF) 攻擊。潛在的攻擊者不僅需要使用者的 Session Cookie,還需要這個帶有時間戳記的秘密 CSRF 令牌,這個令牌會在使用者訪問您應用程式網域上的 URL 時刷新/授予。這讓您可以確信使用者的請求沒有被劫持,並且他們發出的請求是出於本意且合法的。
啟用 CSRF 保護需要管理前端應用程式中的令牌。在傳統的表單提交中,這可以通過在您的 <form>
中將 CSRF 令牌作為隱藏輸入發送來輕鬆完成。或者,更好的做法是在您發送 AJAX 請求時,將 CSRF 令牌作為請求參數或標頭包含進去。要做到這一點,您可以通過向您掛載 security/grant-csrf-token
的路由發送請求來獲取令牌,或者,更好的方法是使用 exposeLocalsToBrowser
partial 從視圖區域變數中獲取令牌。
這裡有一些範例
使用 exposeLocalsToBrowser
partial 從您的客戶端 JavaScript 提供對令牌的訪問,例如:
<%- exposeLocalsToBrowser() %>
<script>
$.post({
foo: 'bar',
_csrf: window.SAILS_LOCALS._csrf
})
</script>
通過向您掛載 security/grant-csrf-token
的路由發送 GET 請求來獲取令牌。它將以 JSON 格式響應,例如:
{ _csrf: 'ajg4JD(JGdajhLJALHDa' }
將令牌直接渲染到 HTML 中的隱藏表單輸入元素中,例如:
<form>
<input type="hidden" name="_csrf" value="<%= _csrf %>" />
</form>
Sails 捆綁了開箱即用的可選 CSRF 保護功能。要啟用內建的強制執行,只需對 sails.config.security.csrf (通常位於您專案的 config/security.js
檔案中) 進行以下調整
csrf: true
您也可以在每個路由的基礎上啟用或停用 CSRF 保護,方法是在您的 config/routes.js
檔案中的任何路由中添加 csrf: true
或 csrf: false
。
請注意,如果您有現有的程式碼與您的 Sails 後端進行通訊 (通過 POST、PUT 或 DELETE 請求),您需要獲取 CSRF 令牌並將其作為參數或標頭包含在這些請求中。稍後會詳細說明。
像大多數 Node 應用程式一樣,Sails 和 Express 與 Connect 的 CSRF 保護中介軟體 相容,用於防範此類攻擊。這個中介軟體實作了 同步器令牌模式。當啟用 CSRF 保護時,所有對 Sails 伺服器的非 GET 請求都必須附帶一個特殊的令牌,通過標頭或查詢字串或 HTTP body 中的參數來識別。
CSRF 令牌是暫時的且特定於 Session 的;例如:想像一下,Mary 和 Muhammad 都是訪問我們在 Sails 上運行的電子商務網站的購物者,並且啟用了 CSRF 保護。假設在星期一,Mary 和 Muhammad 都進行了購買。為了做到這一點,我們的網站需要分發至少兩個不同的 CSRF 令牌 - 一個給 Mary,一個給 Muhammad。從那時起,如果我們的 Web 後端收到一個缺少或不正確令牌的請求,該請求將被拒絕。因此,現在我們可以放心,當 Mary 瀏覽離開去玩線上撲克時,第三方網站無法欺騙瀏覽器使用她的 Cookie 向我們的網站發送惡意請求。
要獲取 CSRF 令牌,您應該在視圖中使用 locals 來引導它 (適用於傳統的多頁 Web 應用程式),或者使用 AJAX 從特殊的受保護 JSON 端點獲取它 (適用於單頁應用程式 (SPA))。
對於老式的表單提交,就像將資料從視圖傳遞到表單動作一樣容易。您可以在視圖中獲取令牌,在那裡可以將其作為視圖區域變數訪問:<%= _csrf %>
例如:
<form action="/signup" method="POST">
<input type="text" name="emailaddress"/>
<input type='hidden' name='_csrf' value='<%= _csrf %>'>
<input type='submit'>
</form>
如果您正在使用表單進行 multipart/form-data
上傳,請務必將 _csrf
欄位放在 file
輸入之前,否則,您將面臨超時和 403 錯誤在檔案完成上傳之前觸發的風險。
在 AJAX/Socket 繁重的應用程式中,您可能更喜歡動態獲取 CSRF 令牌,而不是在頁面上引導它。您可以通過在您的 config/routes.js
檔案中設置指向 security/grant-csrf-token
動作的路由來做到這一點
{
'GET /csrfToken': { action: 'security/grant-csrf-token' }
}
然後向您定義的路由發送 GET 請求,您將獲得以 JSON 格式返回的 CSRF 令牌,例如:
{
_csrf: 'ajg4JD(JGdajhLJALHDa'
}
出於安全原因,您無法通過 Socket 請求檢索 CSRF 令牌。但是,您可以通過 Socket 請求*花費* CSRF 令牌 (見下文)。
security/grant-csrf-token
動作不適用於跨域請求,因為某些瀏覽器預設會阻止第三方 Cookie。有關跨域請求的更多資訊,請參閱 CORS 文件。
一旦您啟用了 CSRF 保護,任何 POST、PUT 或 DELETE 請求 (包括虛擬請求,例如來自 Socket.io 的請求) 發送到您的 Sails 應用程式的請求都需要發送一個隨附的 CSRF 令牌作為標頭或參數。否則,它們將被拒絕並返回 403 (Forbidden) 響應。
例如,如果您正在從帶有 jQuery 的網頁發送 AJAX 請求
$.post('/checkout', {
order: '8abfe13491afe',
electronicReceiptOK: true,
_csrf: 'USER_CSRF_TOKEN'
}, function andThen(){ ... });
對於某些客戶端模組,您可能無法訪問 AJAX 請求本身。在這種情況下,您可以考慮直接在查詢的 URL 中發送 CSRF 令牌。但是,如果您這樣做,請記住在花費令牌之前對其進行 URL 編碼
..., {
checkoutAction: '/checkout?_csrf='+encodeURIComponent('USER_CSRF_TOKEN')
}
- 您可以選擇將 CSRF 令牌作為
X-CSRF-Token
標頭而不是_csrf
參數發送。- 對於大多數開發人員和組織來說,只有在您允許使用者從瀏覽器 (即從您的 HTML/CSS/JavaScript 前端程式碼) 登入/安全訪問您的 Sails 後端時,CSRF 攻擊才需要成為一個問題。如果您不 (例如,使用者僅從您的原生 iOS 或 Android 應用程式訪問受保護的部分),則您可能不需要啟用 CSRF 保護。為什麼? 因為從技術上講,此頁面上討論的常見 CSRF 攻擊僅在使用者使用相同的客戶端應用程式 (例如 Chrome) 訪問不同的 Web 服務 (例如 Chase.com、Horrible-Hacker-Site.com) 的情況下才可能發生。
- 有關 CSRF 的更多資訊,請查看 Wikipedia
- 有關在傳統表單提交中「花費」CSRF 令牌,請參閱上面的範例 (在「使用視圖區域變數」下)。