🛡️ 如何保护Express.js服务器免受CSRF攻击 (代码分析)

大家好!各位Web开发者。💻

今天,我想和大家聊一聊在使用Node.js和Express.js构建Web应用程序时一个非常非常重要但又容易被忽视的安全设置。那就是如何防止CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击。

您是否在中间件配置部分见过以下代码?

// 启用Express CSRF保护
app.use(csrf());

// 使CSRF令牌在模板中可用
app.use((req, res, next) => {
    res.locals.csrftoken = req.csrfToken();
    next();
});

我们将深入浅出地分析这两段代码如何保护我们的服务器,以及它们背后的原理!

🤔 等等,CSRF攻击到底是什么?

在查看代码之前,我们先简单了解一下我们想要阻止的敌人——CSRF是什么。

CSRF是一种让用户在不知情的情况下,执行攻击者意图的操作的攻击。

举个例子吧?

  1. 用户 👤 已登录my-bank.com。浏览器中保留着my-bank.com的认证信息(Cookie)。
  2. 此时,用户点击了恶意邮件或帖子中包含的链接 evil-site.com。😈
  3. evil-site.com 页面中隐藏着看不见的代码。例如,可能隐藏着一个自动发送 my-bank.com/transfer?to=hacker&amount=1000 这样请求的图片标签或表单。
  4. 用户的浏览器在发送此请求时,会自动附带my-bank.com的认证Cookie。🍪
  5. 银行服务器 🏦 呢?它收到包含正常认证Cookie的请求,就会误以为“啊,是正常用户在请求转账啊!”,然后将钱转到攻击者的账户。

太可怕了,不是吗?用户只是点击了一个链接,却在不知不觉中,重要的信息被更改或遭受经济损失,这是一种非常危险的攻击。

🔐 第一步:激活CSRF防御盾!app.use(csrf());

现在我们来看看第一段代码。

// 启用Express CSRF保护
app.use(csrf());

这一行代码就像是为我们的Express服务器安装了一个强大的防御盾。添加这段代码后,服务器将开始执行以下操作:

  • 发放秘密钥匙(CSRF令牌)🔑:服务器为每个用户会话生成一个独一无二且不可预测的字符串,即“CSRF令牌”。这个令牌就像一次性密码。
  • 监控和验证请求 🧐:从现在开始,服务器会检查所有改变状态的请求(POST、PUT、DELETE等),确保其中正确包含了这个秘密钥匙(CSRF令牌)。
  • 如果请求中没有令牌,或者与服务器发放的令牌不匹配怎么办? ➡️ 拦截! ❌ 服务器会将该请求视为危险请求,并抛出错误,拒绝该请求。

现在,即使攻击者 😈 从evil-site.com偷偷发送请求,由于他们不知道这个秘密钥匙(CSRF令牌),服务器也会拦截该请求。这样就可以阻止大部分CSRF攻击!

💌 第二步:安全地将秘密钥匙传递给前端

好了,现在服务器已经准备好创建和验证秘密钥匙了。但是还有一个问题。

“用户正常发送的请求中,如何包含这个秘密钥匙呢?” 🤔

仅仅服务器知道秘密钥匙是不行的。用户端(前端)也需要接收这个钥匙,并在发送请求时一并发送。这正是第二段代码的作用。

// 使CSRF令牌在模板中可用
app.use((req, res, next) => {
    res.locals.csrftoken = req.csrfToken();
    next();
});

这段代码会为每个请求依次执行以下操作:

  1. 调用req.csrfToken():调用csrf()中间件在请求(req)对象上创建的csrfToken()函数。此函数返回当前会话的秘密钥匙(CSRF令牌)。
  2. 存储到res.locals:res.locals是一个特殊的“口袋”,用于存储仅在处理当前请求期间有效的数据。我们将刚刚获取的秘密钥匙以csrftoken的名称存储在这里。
  3. 可在模板中使用:存储在res.locals中的值可以在EJS、Pug、Nunjucks等模板引擎中直接像变量一样使用。这意味着在渲染HTML时,可以轻松地嵌入这个秘密钥匙的值!

✨ 最后一步:在前端使用秘密钥匙

现在所有准备工作都已完成。在前端模板(例如:form.ejs文件)中,可以像下面这样使用存储在res.locals中的令牌:

<form action="/update-profile" method="POST">
  
  <input type="hidden" name="_csrf" value="<%= csrftoken %>">
  
  <div>
    <label for="username">이름:</label>
    <input type="text" id="username" name="username">
  </div>
  
  <button type="submit">프로필 업데이트</button>
</form>

这样,当用户点击“更新个人资料”按钮提交表单时,我们的秘密钥匙(CSRF令牌)就会包含在不可见的 _csrf 字段中并发送到服务器。服务器看到这个令牌后,会判断“啊,这是我发放了秘密钥匙的正常请求!”,然后安全地处理该请求。✅

总结

今天我们探讨的两行代码构建了以下完美的CSRF防御体系:

  1. app.use(csrf());: 生成CSRF令牌,并监控和验证所有传入请求,激活防御盾。
  2. app.use((req, res, next) => { … });: 将防御所需的CSRF令牌安全地传递给前端模板。
  3. 前端: 将接收到的令牌包含在表单的隐藏字段中,作为“我是正常用户”的凭证一并发送给服务器。

Web安全再怎么强调也不为过。特别是对于处理用户重要信息的服务,CSRF防御不是可选项,而是必需项。希望这段代码能帮助您使您的宝贵服务更加安全。

祝您编程愉快!🚀


标签: Nodejs, Express, CSRF, Security, WebDev, csurf, Web安全, Node.js


Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注