模拟实现csrf攻击及预防

注:本文只用于演示,切勿将代码用于非法目的。

csrf 攻击原理

用户登录网站 A,留下 cookie,并保持登录状态 用户打开钓鱼网站 B 钓鱼网站前端向网站 A 后端发送请求,由于网站 A 没有退出登录,所以请求头自动带上网站 A 的 cookie 虽然由于跨域,上面请求的响应会被浏览器拦截,但是请求已经成功发送到网站 A 的后端了

实现

一个简单的办法就是编写一个 login 接口,服务端设置 cookie

前端

通过 axios 发送 http 请求

javascript 复制代码
async login() {
  const res = await axios.post("/login");
}

后端

为了简便,用 koa 实现

使用 ctx.cookies.set 来在响应头设置 cookiecookie 中添加自定义字段 token

javascript 复制代码
router.post("/login", async (ctx, next) => {
  ctx.cookies.set("token", new Date().getTime());
  ctx.set("csrf-token", csrfTokenStore);
  ctx.body = {
    result: "login ok",
  };
});

2. 编写一个有 CSRF 风险的接口:POST /pay

为了避免预检请求失败而阻止正式请求的发出(chrome 104 以前的浏览器有这个现象,详见:专用网络访问规范),我们采用简单请求来实现 postcontent-typeapplication/x-www-form-urlencoded 类型

前端
javascript 复制代码
function pay() {
  axios.post(
    "/pay",
    {
      name: "phish",
    },
    {
      headers: {
        "content-type": "application/x-www-form-urlencoded",
        "csrf-token": this.token,
      },
    }
  );
}
后端

可以获取到请求头中的 cookie 信息

javascript 复制代码
router.post("/pay", async (ctx, next) => {
  const cookie = ctx.request.headers.cookie;
  const array = cookie.split(";");
  const token = array[array.length - 1];

  ctx.body = {
    result: token,
  };
});

3. 创建一个钓鱼网站,为了简单说明 CSRF 攻击的过程,这里只实现前端部分

前端

显示一个按钮,点击后发送钓鱼请求

为了能在请求头中带上 cookieaxios 设置 withCredentials 为 true

根据正常网站的接口设置请求的 content-typeapplication/x-www-form-urlencoded

html 复制代码
<button @click="submit">攻击</button>
javascript 复制代码
import axios from "axios";

axios.defaults.withCredentials = true;

const submit = () => {
  axios.post(
    "http://localhost:8080/pay",
    {
      name: "phish",
    },
    {
      headers: { "content-type": "application/x-www-form-urlencoded" },
    }
  );
};

攻击过程

在正常网站登录,设置cookie

打开钓鱼网站

触发钓鱼请求

这是一个简单请求,所以浏览器没有发送 OPTIONS 预检请求

由于正常网站没有设置允许钓鱼网站跨域,所以跨域请求的响应肯定会被浏览器拦截,但是请求确实已经发到正常网站后端了

钓鱼网站发送的请求头中,带上了正常网站的 cookie ,说明csrf攻击成功

防御

检查 origin 和 referer 和网站域名是否相同

后端示例代码

javascript 复制代码
router.post("/pay", async (ctx, next) => {
  const cookie = ctx.request.headers.cookie;
  const array = cookie.split(";");
  const token = array[array.length - 1];
  const csrfToken = ctx.request.headers["csrf-token"];
  const referer = ctx.request.headers.referer;
  console.log(csrfToken);
  if (referer !== "http://localhost:8080") {
    // 新增代码
    ctx.status = 400;
    return;
  }
  ctx.body = {
    result: token,
  };
});

运行结果

优缺点

优点 缺点
实现简单,origin 和 referer 是浏览器添加的请求头,无法通过前端代码修改 把安全性交给各个浏览器厂商来保证,还是存在被篡改的风险

例子

调/login 接口时,后端设置 sameSite 参数,值为 srtict 或者 lax,详细区别见:SameSite

javascript 复制代码
router.post("/login", async (ctx, next) => {
  ctx.cookies.set("token", new Date().getTime(), {
    httpOnly: false,
    sameSite: "strict",
  }); // 增加sameSite参数
  ctx.body = {
    result: "login ok",
  };
});

效果

钓鱼网站发送的请求,请求头没有带上正常网站的 cookie

备注:用 chrome 浏览器,localhost 下的不同端口号还是会带上不同域的 cookie,所以这里正常网站用 http://localhost:8080,钓鱼网站用http://192.168.1.102:8081 演示

优缺点

优点 缺点
前端代码无感知,由浏览器决定是否带上 cookie 对于 ip 相同,端口号不同的域,chrome 还是会带上 cookie,存在安全风险

csrf token

  1. 登录时,接口返回 csrf token(可以放到 cookie 或者响应头字段中)
  2. 前端把 csrf token 保存到变量中
  3. 后续发请求时,在请求头中添加 csrf token
示例 : csrf token 保存在响应头中

登录时,后台在响应头设置 csrf token

javascript 复制代码
router.post("/login", async (ctx, next) => {
  ctx.cookies.set("token", new Date().getTime(), {
    httpOnly: false,
  });
  ctx.set("header-csrf-token", csrfTokenStore); // 这里后端定义好token的值,并缓存下来
  ctx.body = {
    result: "login ok",
  };
});

前端从登录请求的响应头中获取到 csrf token

javascript 复制代码
async login() {
      const res = await axios.post("/login");
      const token = res.headers.get("header-csrf-token");
      console.log("csrf-token from header: ", token);
      this.token = token; // 前端应用中保存csrf token
    },

后续发送请求时,在前端设置请求头,带上 csrf token

javascript 复制代码
pay() {
      axios.post(
        "/pay",
        {
          name: "phish",
        },
        {
          headers: {
            "content-type": "application/x-www-form-urlencoded",
            "header-csrf-token": this.token,
          },
        }
      );

后端校验请求头的 csrf token 是否和后端缓存的 csrf token 一致

javascript 复制代码
router.post("/pay", async (ctx, next) => {
  const cookie = ctx.request.headers.cookie;

  const headerCsrfToken = ctx.request.headers["header-csrf-token"];
  const referer = ctx.request.headers.referer;
  console.log(headerCsrfToken);

  if (headerCsrfToken !== csrfTokenStore) {
    ctx.status = 400;
    return;
  }

  ctx.body = {
    result: headerCsrfToken,
  };
});

验证

钓鱼网站无法获取 csrf token,所以请求失败

备注

本地验证时,如果用 http://localhost:8081/,前端仍然可以通过 document.cookie 拿到 http://localhost:8080 下的 cookie 字段,所以没法验证 csrf token 保存到 cookie 中的场景

验证码

在进行敏感操作前,把验证码通过短信发给用户,以保证一定是用户本人进行操作。

优缺点

优点 缺点
安全性高 操作较繁琐,对用户不太友好
相关推荐
NiNg_1_23412 分钟前
Echarts连接数据库,实时绘制图表详解
前端·数据库·echarts
如若1231 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~2 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语2 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport2 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg2 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww2 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254882 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
浏览器爱好者2 小时前
谷歌浏览器的网络安全检测工具介绍
chrome·安全
唐 城2 小时前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust