谈谈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
<template>
    <div id="app">
        <div>
            <button v-on:click="login">登录</button>
            <a>{{message1}}</a>
        </div>
        <div>
            <button v-on:click="count">计数</button>
            <a>{{message2}}</a>
        </div>
    </div>
</template>

<script>
export default {
    name: 'app',
    data() {
        return {
            message1: '',
            message2: '',
        }
    },
    methods: {
        login() {
            fetch('http://localhost:7001/login', { method: 'POST', credentials: 'include' })
                .then(res => {
                    return res.json();
                }).then(json => {
                    this.message1 = json;
                }).catch(error => {
                    this.message1 = error;
                });
        },
        count() {
            fetch('http://localhost:7001/count', { method: 'POST', credentials: 'include' })
                .then(res => {
                    return res.json();
                }).then(json => {
                    this.message2 = json;
                }).catch(error => {
                    this.message2 = error;
                });
        },
    }
}
</script>

<style>
</style>

  • 测试

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

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

spa-csrf-01.png

攻击

vue create csrf-client

cd csrf-client

yarn serve
vim src/App.vue
<template>
    <div id="app">
        <form action="http://localhost:7001/count" method="POST">
            <input type="submit" value="点击中大奖">
        </form>
    </div>
</template>

<script>
export default {
    name: 'app',
}
</script>

<style>
</style>

  • 测试

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

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

spa-csrf-02.png
spa-csrf-03.png

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

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

spa-csrf-04.png

小结

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

  • 条件1: 绕过浏览器跨域限制 例如: 上述<form>标签 详见Laravel框架 之 CSRF

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

对于前后端未分离的项目

  • 条件1 无法回避

  • 条件2 可以在<form>或<meta>添加隐藏的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进行权限校验

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容

  • 小确幸/感恩:昨晚陪萌读书,发现孩子成语接龙前100个成语中大部分会背了,而且好几个成语能接上两个成语:如:用舍行...
    书琴001阅读 154评论 0 0
  • 尊敬的老师们,全天下最爱学习最有能量的伙伴们:大家现在好!我是你们最值得信赖的朋友忠伟姐姐。通过这几天的学习和交流...
    小忠伟阅读 400评论 0 5
  • 关于鸡蛋哥哥的介绍 他不在意弟弟比他大 因为呆在鸡蛋里好玩的事很多 比如 可以躲在各种地方捉迷藏(看到鸡蛋哥哥躲在...
    秀琴sukin阅读 1,940评论 0 2