本文示例基于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
点击"登录"和"计数"按钮后 效果如下
攻击
vue create csrf-client
cd csrf-client
yarn serve
vim src/App.vue
- 测试
使用浏览器打开http://localhost:8081
点击"点击中大奖"按钮后 效果如下
使用浏览器打开http://localhost:8080
点击"计数"按钮后 效果如下
小结
通过上述示例 我们知道想要成功进行CSRF攻击有如下两个条件
条件1: 绕过浏览器跨域限制 例如: 上述
条件2: 基于cookie存储的session鉴权 AJAX请求会自动带上cookie导致鉴权通过
对于前后端未分离的项目
条件1 无法回避
条件2 可以在
而对于前后端分离的项目
条件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