分享个人 Full-Stack JavaScript 项目开发经验
跨站请求伪造(cross-site request forgery,CSRF)是指攻击者欺骗用户浏览器,让其以用户的名义运行操作。这种跨站请求依赖于浏览器同源策略中允许跨域写操作和跨域资源嵌入的开放规则,如链接、表单提交、css 的 url 和<img>嵌入等方式。
攻击示例的大致过程:用户使用同一浏览器访问两个站点。用户先在A站点登录,并获得会话凭据。然后访问B站点,其中B站点中预设了一个提交给A站点的包含恶意操作的<form>表单,并通过脚本的 submit 方法自动提交。当用户访问B站点时,将会使用此时用户在A站点的会话凭据来请求A站点,于是通过了身份验证并执行恶意操作。
通过检查 Referer 请求头来判断请求来源地址的方法并不是那么可靠,所以通常会使用添加校验 token 的方式来防范 CSRF。下面介绍 koa2 框架适用的 koa-csrf 模块是如何帮助我们实现这一功能。
通过 yarn 安装:
yarn add koa-csrf
使用 koa-csrf 的 CSRF 类实例化中间件:
const CSRF = require('koa-csrf');
module.exports = new CSRF({
invalidTokenMessage: 'Invalid CSRF token',
invalidTokenStatusCode: 403,
excludedMethods: ['GET', 'HEAD', 'OPTIONS'],
disableQuery: true
});
参数的说明如下:
invalidTokenMessage
使 koa 抛出的错误信息内容,默认值为:'Invalid CSRF token'。它可以是一个接收 ctx 作为参数的函数,函数最后返回错误信息内容。
invalidTokenStatusCode
验证失败时的响应状态码,默认值为:403(Forbidden)。跟 invalidTokenMessage 参数一样,它也会被传递给 ctx.throw,用于抛出错误和拒绝请求。
excludedMethods
排除的请求方法,默认值为:['GET', 'HEAD', 'OPTIONS']。
disableQuery
是否禁止通过查询字符串传递 _csrf 校验 token,默认值为 false。如果校验 token 出现在 URL 中,则可能会通过 Referer 泄露,应尽量把 Token 放在表单中,把敏感操作由 GET 改为 POST。
app.js 中添加 session 支持。
const session = require("koa-session");
// ...
app.keys = [process.env.COOKIE_SECRET];
app.use(session(CONFIG, app));
// ...
在路由级别应用 csrf 限制:
const router = require('koa-router')();
const controllers = require('../controllers');
const {authenticate} = require("../lib/middleware/auth");
const csrf = require('../lib/middleware/csrf');
router.post('/clear', authenticate, csrf, controllers.admin.clear.apiClear);
在路由控制器中获取校验 token 的值:
module.exports = async (ctx, next) => {
// console.log(ctx.csrf);
// .....
}
请求中提交校验 token 的字段名称为 _csrf。如果你的表单中包含文件上传,使用了 encType="multipart/form-data",开始时 koa-bodyparser 不能处理这样的请求体,则可以通过 x-csrf-token 请求头来提交校验 token,校验通过后再使用 koa-multer 来处理请求体。
koa-csrf 存放在 session 中的 secret 和传送到窗体中的 token 都是通过csrf模块来创建和比对的。流程如下:
CSRF 攻击者可以利用用户的会话凭据向服务器发送 session.secret,但不会知道它的具体值,所以没办法生成对应的 token,从而请求失败。