这是我第一次实现 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 两次为您第一时间 .
// 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
时的方法是 null
或 undefined
这是第一次发生。你可以看到 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/