跨站请求伪造(CSRF)是一种攻击类型,它迫使最终用户在当前已认证的 Web 应用程序后端执行不需要的操作。换句话说,如果没有保护措施,存储在浏览器(如 Google Chrome)中的 Cookie 可以用来从用户的计算机向 Chase.com 发送请求,无论该用户当前是否正在访问 Chase.com 或 Horrible-Hacker-Site.com。
CSRF 令牌就像限量版赠品。会话告诉服务器用户“就是他们自称的那个人”,而 CSRF 令牌则告诉服务器他们“确实在他们自称的地方”。当在您的 Sails 应用程序中启用 CSRF 保护时,所有非 GET 请求都必须附带一个特殊的“CSRF 令牌”,该令牌可以作为 '_csrf' 参数或 'X-CSRF-Token' 标头包含在内。
使用令牌可以保护您的 Sails 应用程序免受跨站请求伪造 (CSRF) 攻击。潜在的攻击者不仅需要用户的会话 Cookie,还需要这个带时间戳的秘密 CSRF 令牌,该令牌会在用户访问应用程序域上的 URL 时刷新/授予。这使您可以确定用户的请求未被劫持,并且他们发出的请求是故意的和合法的。
启用 CSRF 保护需要在前端应用程序中管理令牌。在传统的表单提交中,可以通过在您的 <form>
中添加一个隐藏的输入来轻松实现这一点。或者更好的是,在发送 AJAX 请求时将 CSRF 令牌作为请求参数或标头包含在内。为此,您可以通过向挂载 security/grant-csrf-token
的路由发送请求来获取令牌,或者更好的是,使用 exposeLocalsToBrowser
部分从视图局部变量中获取令牌。
以下是一些示例
使用 exposeLocalsToBrowser
部分从客户端 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
您还可以通过在 config/routes.js
文件中的任何路由中添加 csrf: true
或 csrf: false
来按路由启用或禁用 CSRF 保护。
请注意,如果您有通过 POST、PUT 或 DELETE 请求与 Sails 后端通信的现有代码,则需要获取 CSRF 令牌并将其作为参数或标头包含在这些请求中。稍后会详细介绍。
与大多数 Node 应用程序一样,Sails 和 Express 与 Connect 的 CSRF 保护中间件 兼容,以防止此类攻击。此中间件实现了 同步器令牌模式。启用 CSRF 保护后,所有发送到 Sails 服务器的非 GET 请求都必须附带一个特殊令牌,该令牌由查询字符串或 HTTP 正文中的标头或参数标识。
CSRF 令牌是临时的且特定于会话的;例如,假设 Mary 和 Muhammad 都是访问在我们基于 Sails 运行的电子商务网站上的购物者,并且启用了 CSRF 保护。假设在周一,Mary 和 Muhammad 都进行了购买。为此,我们的网站需要分发至少两个不同的 CSRF 令牌——一个给 Mary,一个给 Muhammad。从那时起,如果我们的 Web 后端收到缺少或不正确的令牌的请求,则该请求将被拒绝。因此,现在我们可以放心,当 Mary 导航到其他地方玩在线扑克时,第三方网站无法欺骗浏览器使用她的 Cookie 向我们的网站发送恶意请求。
要获取 CSRF 令牌,您应该在视图中使用 局部变量 引导它(适用于传统的网页应用程序),或者从特殊的受保护的 JSON 端点使用 AJAX 获取它(适用于单页应用程序 (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'
}
出于安全原因,您无法通过套接字请求检索 CSRF 令牌。但是,您可以通过套接字请求使用 CSRF 令牌(见下文)。
security/grant-csrf-token
操作并非旨在用于跨源请求,因为某些浏览器默认阻止第三方 Cookie。有关跨源请求的更多信息,请参阅 CORS 文档。
启用 CSRF 保护后,任何发送到 Sails 应用程序的 POST、PUT 或 DELETE 请求(包括虚拟请求,例如来自 Socket.io 的请求)都需要将附带的 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 的更多信息,请查看 维基百科
- 有关在传统表单提交中“使用”CSRF 令牌,请参阅上面的示例(在“使用视图局部变量”下)。