javascript - CSRF 在第一次发布尝试时不起作用

标签 javascript node.js express csrf

这是我第一次实现 CSRF,也是第一次在 Stack 上发帖。我一直在努力通过 CSRF 配置,但最终得到了几乎可以工作的东西。
如果我在新浏览器中打开带有书签的页面并提交表单,我会看到 403 Invalid CSRF 错误:EBADCSRFTOKEN。在这种情况下,用户身份验证是 cookie,因此它不会挑战。我想知道 session 是否已过期?后续帖子工作正常。获取请求都很好。我很困惑,是时候把这个挑战放在一边并寻求帮助,因为我已经做了太久了,将不胜感激任何帮助。
Server.js 不引用 csrf 中间件但设置 session

const express = require("express");
const path = require("path");
const favicon = require("serve-favicon");
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const flash = require("connect-flash");
const mongoose = require("mongoose");
const logger = require("morgan");
const expressSession = require("express-session");
const setCurrentUser = require("./app/controllers/setCurrentUser");

var session = {
  secret: "XXXHIDDENXXX",
  cookie: {},
  resave: false,
  saveUninitialized: false,
};

app.use(favicon(path.join(__dirname, "public/images", "favicon.png")));
app.use(logger("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(flash());
app.use(expressSession(session));
app.use("/public", express.static("public"));
app.use(setCurrentUser);

app.use((req, res, next) => {
  res.locals.user = req.user;
  res.locals.success = req.flash("success");
  res.locals.error = req.flash("error");
  next();
});
索引.js
"use strict";

var express = require("express");
var router = express.Router();
const csrf = require("csurf");
const isLoggedIn = require("../controllers/auth");

var csrfProtection = csrf({ cookie: true });

router.use(csrf({ cookie: true }));

router.use((req, res, next) => {
  // generate one CSRF token to every render page
  res.locals.token = req.csrfToken();
  next();
});

router.get("/settings", isLoggedIn, csrfProtection, function (req, res, next) {
  ...
});

router.post("/settings", isLoggedIn, csrfProtection, function (req, res, next) {
  ...
});

接口(interface),我用的是EJS,相关代码:
<head>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="<%= locals.token %>">
</head>

<form method="POST" action="/settings">
<input type="hidden" name="_csrf" value="<%= locals.token %>">
<div class="form-group">
<label for="displayName">Name</label>
<input type="text" name="displayName" id="displayName" maxlength='30' data-parsley-maxlength='30' class="form-control" value="<%= user.displayName %>" required>
</div>
<button class="btn btn-primary">Submit</button>
</form>
上面的代码按预期呈现:
<meta name="csrf-param" content="authenticity_token">
<meta name="csrf-token" content="qBdXd2ZQ-8vnbyw_IUivar_6UKp0qvhUf290">
<input type="hidden" name="_csrf" value="qBdXd2ZQ-8vnbyw_IUivar_6UKp0qvhUf290">
token 通过 post 数据中的表单 post 发送为 _csrf:qBdXd2ZQ-8vnbyw_IUivar_6UKp0qvhUf290

最佳答案

编辑 1
TL;博士
您在 cookie 模式下使用了两次 csrf 中间件,它第一次使 express set cookie 两次。 (你可以看到有两个 token )
你给你的ejs token1 ,但您的 express 使用 token2 验证它在此行 app.use(csrf({ cookie: true })) .
这就是发生无效 token 的原因。
但是,它只会发生 第一时间因为根本原因取决于 csurf 包的实现。
如果您想知道根本原因,您可以查看解释以获得更多详细信息。

app.use(csrf({ cookie: true }))
app.use((req, res, next) => {
  // token1
  res.locals.token = req.csrfToken();
  next();
});
const csrfProtection = csrf({ cookie: true })
//                   token2 
app.get('/settings', csrfProtection, function (req, res) {
  res.render('send')
})
那么如何解决呢?
删除 var csrfProtection = csrf({ cookie: true });这条线。
并删除路由器中的“设置”中间件
router.get("/settings", isLoggedIn, csrfProtection, function (req, res, next) {
  ...
});
router.get("/settings", isLoggedIn, function (req, res, next) {
  ...
});

解释
很抱歉粘贴了这么长的代码,但我需要它来解释为什么你的代码在第一次发布时就被破坏了。
请参阅以下代码中的 mark1 和 mark2。
Mark1:你用app.use(csrf({ cookie: true }))这意味着无论路径匹配与否,您都为路由器的其余部分提供中间件。
Mark2:您只提供csrfProtection在您的特定路由器中,即“/settings”路径,而不是路由器的其余部分。
如果一起使用呢?
express 会设置-cookie 两次为您第一时间 .
enter image description here
// server.js
const cookieParser = require('cookie-parser')
const csrf = require('csurf')
const bodyParser = require('body-parser')
const express = require('express')
const session = require("express-session")
const app = express()
const sess = {
  secret: 'Key',
  resave: false,
  saveUninitialized: true,
  cookie: {}
}
app.set("view engine", "ejs")
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(session(sess))

// ====> mark1
app.use(csrf({ cookie: true }))
app.use((req, res, next) => {
  res.locals.token = req.csrfToken();
  next();
});

// ====> mark2
const csrfProtection = csrf({ cookie: true })

// ====> mark2
app.get('/settings', csrfProtection, function (req, res) {
  res.render('send')
})

// ====> mark2
app.post('/settings', csrfProtection,  function (req, res) {
  res.send('data is being processed')
})
app.listen(8080)
<!-- my send.ejs -->
<form method="POST" action="/settings">
  <input type="hidden" name="_csrf" value="<%= locals.token %>">
  <button class="btn btn-primary">Submit</button>
</form>
这就是您第一次获得无效 csrf token 的原因。
然后看csurf的源代码。
它将使用 setSecret secret 时的方法是 nullundefined这是第一次发生。
你可以看到 csurf 使用 setHeader这里。
// here, it's a middleware you used.
return function csrf (req, res, next) {
  // .... other code

  // generate & set secret
  if (!secret) {
    secret = tokens.secretSync()
    setSecret(req, res, sessionKey, secret, cookie)
  }
}

// setSecret will go to this method
function setCookie (res, name, val, options) {
  var data = Cookie.serialize(name, val, options)

  var prev = res.getHeader('set-cookie') || []
  var header = Array.isArray(prev) ? prev.concat(data)
    : [prev, data]

  res.setHeader('set-cookie', header)
}
您可以添加 console.log("secret: " + secret)在 csurf 模块的 index.js 的第 105 行
然后你会看到下面的日志 set-cookie 被触发两次 .
secret: undefined
secret: undefined
这就是您第一次发帖被破坏的原因,因为您同时使用了以下两种方法。
// ====> method1
app.use(csrf({ cookie: true }))
app.use((req, res, next) => {
  res.locals.token = req.csrfToken();
  next();
});

// ====> method2
const csrfProtection = csrf({ cookie: true })
app.get('/settings', csrfProtection, function (req, res) {
  res.render('send')
})
原始答案
我不确定你在找什么。
但是如果你在谈论为什么 GET请求不受 csurf 保护, 因为 GET csurf 忽略了请求在默认情况下。
您可以在 here 查看源代码
// ignored methods
var ignoreMethods = opts.ignoreMethods === undefined
    ? ['GET', 'HEAD', 'OPTIONS']
    : opts.ignoreMethods
顺便说一句,GET request 通常用于只读操作。
OWASP CSRF

Do not use GET requests for state changing operations.


我认为这就是 csurf 的原因。不保护 GET默认请求。

关于javascript - CSRF 在第一次发布尝试时不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64869650/

相关文章:

javascript - Node 和MySQL-connection.query内部的connection.query-无法访问对象属性

javascript - 在 X 小时后使我的链接过期

node.js - Nodejs : restore a deleted file with fs. 取消链接

javascript - 无法在 Express + Passport 应用程序/身份验证测试中发布

javascript - 如何在 fs.existsSync 或 statSync 或 readFileSync 内部使用通配符

javascript - 如何从外部url加载html源模板

javascript - 为什么当我使用像字符串这样的 JSON 传递参数时,ExternalInterface 会中断?

javascript - 查询/CSS : Unable to set position of an element using offset() value of another element?

javascript - Passport 错误 - Strategy.ParseErrorResponse

node.js + Express ,单独文件中的路由器和仅在某些路由之前的中间件?