CSRF在第一次发布尝试后不起作用 [英] CSRF doesn't work on the first post attempt

查看:56
本文介绍了CSRF在第一次发布尝试后不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我第一次实现CSRF,也是第一次在Stack上发布.我在CSRF配置中苦苦挣扎,但最终得到了几乎可以解决的问题.

This is my first time implementing CSRF and first time posting on Stack. I've struggled through the CSRF config, but finally got something that almost works.

如果我在新的浏览器中打开带书签的页面并提交表单,则会看到403无效的CSRF错误:EBADCSRFTOKEN.在这种情况下,用户auth被cookie,因此它不会挑战.我想知道会话是否过期?随后的帖子工作正常.获取请求都很好.我很沮丧,有时间搁置这一挑战并寻求帮助了,因为我已经从事了太久了,将不胜感激.

If I open a bookmarked page in a fresh browser and submit a form, I'm seeing a 403 Invalid CSRF error: EBADCSRFTOKEN. In this case, the user auth is cookied so it does not challenge. I wonder if the session is expired? Subsequent posts work fine. Get requests are all fine. I'm stumped, time to put this challenge aside and ask for help as I've been at it for way too long, would appreciate any help.

Server.js不引用csrf中间件,而是设置会话

Server.js does not reference csrf middleware but sets up the 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();
});

Index.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) {
  ...
});

接口,我正在使用EJS,相关代码:

Interface, i'm using EJS, relevant code:

<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>

上面的代码按预期方式呈现:

The above code renders as expected:

<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">

令牌在表单数据中以_csrf的形式通过表单发布发送:qBdXd2ZQ-8vnbyw_IUivar_6UKp0qvhUf290

Token is sent via the form post in the post data as _csrf: qBdXd2ZQ-8vnbyw_IUivar_6UKp0qvhUf290

推荐答案

编辑1

TL; DR

您在cookie模式下两次使用了csrf中间件,它第一次使Express Set cookie两次.(您会看到有两个令牌)
您给ejs token1 ,但您的快递在此行 app.use(csrf({cookie:true}))中使用 token2 对其进行验证.

EDIT 1

TL;DR

You use csrf middleware twice in cookie mode, it make express set cookie twice at the first time. (you could see there are two tokens)
You give your ejs token1, but your express validate it with token2 in this line app.use(csrf({ cookie: true })).

这就是无效令牌发生的原因.但是,它只会在第一次时发生,因为根本原因取决于csurf软件包的实现.
如果您想了解根本原因,可以查看解释以获取更多详细信息.

That's why invalid token happened.
But, it only happen at the first time because root cause depend on the implementation of csurf package.
You could see explanation to get more detail if you want to know the root cause.

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}); 此行.
并删除路由器中设置"的中间件

So how to fix it?

Remove var csrfProtection = csrf({ cookie: true }); this line.
And remove the middleware in your router which is "settings"

来自

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

收件人

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

说明

很抱歉粘贴此长代码,但我需要它来解释为什么您的代码在第一次发布时就被破坏了.

Explaination

Sorry for pasting this long code, but I need it to explain why your code is broken in first time post.

请在以下代码中查看mark1和mark2.

Please see mark1 and mark2 in following code.

Mark1:您使用 app.use(csrf({cookie:true}))表示您为路由器的其余部分提供了中间件.

Mark1: you use app.use(csrf({ cookie: true })) that means you provide a middleware for rest of router no matter path matches or not.

Mark2:您只能在特定路由器中提供 csrfProtection ,即"/settings"路径,而不是路由器的其余部分.

Mark2: you only provide the csrfProtection in your specific router which is "/settings" path, not rest of router.

如果一起使用怎么办?
Express会在第一次 时为您设置Cookie 两次.

What if you use it together?
Express will set-cookie twice for you in the first time.

// 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令牌的原因.

That's why you got the invalid csrf token in first time.

然后查看csurf的源代码.
secret null undefined 时(第一次发生),它将使用 setSecret 方法.
您可能会看到csurf在这里使用 setHeader .

Then look at source code of the csurf.
It will use setSecret method when secret is null or undefined which is happening at first time.
And you could see csurf use setHeader here.

// 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)
}

您可以在csurf模块的index.js中的第105行中添加 console.log("secret:" + secret)
那么您将看到以下日志,该日志两次触发了 set-cookie .

You could add console.log("secret: " + secret) in line 105 in index.js of csurf modules
then you'll see the following log which set-cookie is triggered twice.

secret: undefined
secret: undefined

这就是为什么您第一次发布的帖子被打断,因为您同时使用了以下两种方法.

That's why your first time post is broken because you use both following methods at the same time.

// ====> 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')
})

原始答案

我不确定您要寻找什么.

Original Answer

I'm not sure what you're looking for.

但是,如果您在谈论为什么 GET 请求不受 csurf 保护,因为 GET 请求被 csurf忽略默认情况下.您可以在此处

But if you're talking about why GET request is not protected by csurf , because GET request is ignored by csurf in default. You could see the source code at here

// ignored methods
var ignoreMethods = opts.ignoreMethods === undefined
    ? ['GET', 'HEAD', 'OPTIONS']
    : opts.ignoreMethods

顺便说一句, GET 请求通常用于只读操作.

By the way, GET request is usually used in read-only operations.

OWASP CSRF

请勿将GET请求用于状态更改操作.

Do not use GET requests for state changing operations.

我认为这是 csurf 在默认情况下不保护 GET 请求的原因.

I think that is a reason why csurf doesn't protect the GET request in default.

这篇关于CSRF在第一次发布尝试后不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆