谈谈SPA的CSRF问题

本文示例基于Vue.js + Egg.js 代码参考csrf

目录

  • 示例

    • 服务

    • 跨域

    • 前端

    • 攻击

  • 小结

  • 问题

示例

首先 我们通过如下示例来看一下SPA(单页面应用)的CSRF攻击

服务

cnpm i -g egg-init
egg-init --type=simple saas-server

cd saas-server && cnpm i

cnpm run dev
vim app/router.js
'use strict';

let count = 0;

module.exports = app => {
  const { router } = app;
  router.post('/login', async ctx => {
    ctx.session.user = 'user';
    ctx.body = { message: 'login success' };
    ctx.status = 200;
  });
  router.post('/count', async (ctx, next) => {
    if (!ctx.session.user) {
      ctx.body = { message: 'need login' };
      ctx.status = 403;
      return;
    }
    await next();
  }, async ctx => {
    ctx.body = { message: 'count ' + count++ };
    ctx.status = 200;
  });
};

vim config/config.default.js
'use strict';

module.exports = appInfo => {
  const config = exports = {};

  config.keys = appInfo.name + '_1533543342201_7043';

  config.middleware = [];

  config.security = {
    csrf: {
      enable: false,
    },
  };

  return config;
};

  • 测试
curl -X POST localhost:7001/count # {"message":"need login"}

curl -c cookies -X POST localhost:7001/login # {"message":"login success"}

curl -b cookies -X POST localhost:7001/count # {"message":"count 0"}

跨域

cnpm i --save egg-cors
vim config/plugin.js
'use strict';

exports.cors = {
  enable: true,
  package: 'egg-cors',
};

vim config/config.default.js
'use strict';

module.exports = appInfo => {
  const config = exports = {};

  config.keys = appInfo.name + '_1533543342201_7043';

  config.middleware = [];

  config.security = {
    csrf: {
      enable: false,
    },
  };

  config.cors = {
    credentials: true,
    origin: 'http://localhost:8080',
    allowMethods: 'HEAD,OPTIONS,GET,PUT,POST,DELETE,PATCH',
  };

  return config;
};

前端

cnpm i -g @vue/cli
vue create saas-client

cd saas-client

yarn serve
vim src/App.vue






  • 测试

使用浏览器打开http://localhost:8080

点击"登录"和"计数"按钮后 效果如下

谈谈SPA的CSRF问题_第1张图片
spa-csrf-01.png

攻击

vue create csrf-client

cd csrf-client

yarn serve
vim src/App.vue






  • 测试

使用浏览器打开http://localhost:8081

点击"点击中大奖"按钮后 效果如下

谈谈SPA的CSRF问题_第2张图片
spa-csrf-02.png
谈谈SPA的CSRF问题_第3张图片
spa-csrf-03.png

使用浏览器打开http://localhost:8080

点击"计数"按钮后 效果如下

谈谈SPA的CSRF问题_第4张图片
spa-csrf-04.png

小结

通过上述示例 我们知道想要成功进行CSRF攻击有如下两个条件

  • 条件1: 绕过浏览器跨域限制 例如: 上述

    标签 详见Laravel框架 之 CSRF

  • 条件2: 基于cookie存储的session鉴权 AJAX请求会自动带上cookie导致鉴权通过

对于前后端未分离的项目

  • 条件1 无法回避

  • 条件2 可以在或添加隐藏的csrf-token来保证请求的有效性 详见CSRF 保护

而对于前后端分离的项目

  • 条件1: 同样无法回避

  • 条件2: 可以通过使用除cookie外的其他浏览器存储 例如: sessionStorage或localStorage

因此 对于前后端分离的SPA应用 推荐使用基于非cookie存储的token鉴权 详见JWT入门 和 Laravel框架 之 Passport

问题

将token存储于sessionStorage或localStorage中 会引起共享问题

例如 cookie的域名为".yourdomain.com" 那么"yourdomain.com"和"app.yourdomain.com"都可以访问该cookie

但是 存储于sessionStorage或localStorage中的token却不能在不同域名(甚至subdomain)中共享

因此

  • 可以将token存储于域名为".yourdomain.com"的cookie中

并且

  • 此时的cookie只做存储而非鉴权 即服务端并不依赖request中的cookie进行权限校验

参考

  • 10 Things You Should Know about Tokens

你可能感兴趣的:(谈谈SPA的CSRF问题)