【问题标题】:getting CSRF errors from PUT fetch request that does not involve Flask forms从不涉及 Flask 表单的 PUT 获取请求中获取 CSRF 错误
【发布时间】:2021-05-05 04:45:48
【问题描述】:

我在我的 Flask webapp 中收到来自常规 PUT 请求的 CSRF 错误。
我不明白为什么会出现这些错误,因为 PUT 请求不涉及任何形式。

我关注了文档here 并像这样使用 CSRF 保护初始化我的应用程序:

cat app/__init__.py
...
from flask_wtf.csrf import CSRFProtect, generate_csrf, validate_csrf
...
csrf = CSRFProtect()
...
def create_app(config_class=Config):
    app = Flask(__name__)
...
    csrf.init_app(app)
...

来自 javascript 的调用是

cat js/js_script1.js
...
        queryUrl = 'https://localhost/put_request1';
        let fetchData = { 
            method: 'PUT'
        };
        let response = await fetch(queryUrl, fetchData);

我在浏览器和后端看到错误 - Error: 400 Bad Request: The CSRF token is missing.

文档主要讨论表单上下文中的 CSRF 和 ajax,但在这种情况下,我使用的是 fetch(在这件事上 fetch 与 ajax 相同吗?)并且没有任何形式。

this link部分中:CSRF令牌是如何构造的有一个代码sn-p,它使用flask_wtf函数generate_csrf创建csrf_token。 我试图在 app/__init__.py 中实现它,这导致了其他不相关的错误 (RuntimeError: Working outside of request context)。

here 中,csrf_token 是从 html 页面中的“csrf-token”元素中读取的。

const csrfToken = document.querySelector("[name='csrf-token']").content

但我没有 csrf-token

为了比较,我打开了我的其他页面之一,它确实有一个表单,在那里我确实看到了 csrf_token 元素:

<input id="csrf_token" name="csrf_token" type="hidden" value="ImI...">

chrome 调试器 -> 应用程序 -> cookie -> https://localhost cookie 我只看到 session 令牌,但看不到 csrf_token

问题:

  • 初始化我的 Flask 应用程序时是否需要生成 csrf_token?
    (据我了解,Flask 会自动为 FlaskForm 执行此操作,但在这种情况下,我没有涉及 FlaskForm)
  • 如果确实需要生成 csrf_token,如何为常规 POST/PUT 提取请求生成 csrf_token?

编辑 1:

根据@Detlef 的回复,我又做了一次尝试。
我尝试遵循here 中的 Javascript 请求指南以使用以下命令获取 csrf_token,但未成功: var csrf_token = "{{ csrf_token() }}";

我了解如果此代码嵌入到 html 页面中,那么 jinja2 将从函数csrf_token()返回的值代入变量csrf_token

在我的例子中,我从 javascript 文件(包含在 html 页面中)发出 fetch 请求。
所以我不明白在这种情况下如何处理命令。

我对 javascript 代码进行了以下更改:

        var csrf_token = "{{ csrf_token() }}";
        console.log('csrf_token', csrf_token); 
        
        let headersData = {
            'Content-Type': 'application/json',
            'X-CSRFToken': csrf_token,
            'X-CSRF-TOKEN': csrf_token,
            'X-CSRF-Token': csrf_token,
        };

        let fetchData = { 
            method: 'PUT',
            headers: headersData
        };

        queryUrl = 'https://localhost/put_request1';
        let response = await fetch(queryUrl, fetchData);

控制台中显示的 csrf_token 变量的值是
csrf_token: {{ csrf_token() }}
这显然是错误的(没有任何解释)。
这会导致浏览器和后端出现另一个 CSRF 错误:

Error: 400 Bad Request: The CSRF token is invalid.

编辑 2:

根据@Detlef I 的指导:

  • 将元标记添加到 .html 页面
<meta content="{{ csrf_token() }}" name="csrf-token">
  • 检索了 javascript 中的 csrf_token 使用:
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");

这解决了问题。
我没有看到 CSRF 错误。
我还在 chrome-devtools -> Networks 中检查了失败的请求,并且确实在请求标头 x-csrf-token 中看到了预期。

x-csrf-token: ImI0ODY...

【问题讨论】:

  • 我已经更新了我的答案。我希望这会有所帮助。
  • @Detlef,它确实解决了我的问题。谢谢!

标签: flask csrf csrf-token


【解决方案1】:

javascript fetch api 应该使 ajax 请求更容易。
所以,是的 fetch 是一个 ajax 请求。

在您的烧瓶应用程序初始化期间不会生成 CSRF 令牌。它与请求更相关。关键是,攻击者可以接管其他人的会话并代表他们发送请求。据我所知,它应该是每个请求的令牌,并确保对方客户端仍然是发出前一个 GET 请求的人。请同时查看this
关于 ajax 请求何时以及如何更新 CSRF 令牌有各种讨论。我不是安全专家,目前不想参与此活动。

将令牌保存在 html 元元素中是 我认为一种明智的方法,就我而言,可以在您的情况下使用它。您还可以将令牌直接分配给 javascript 变量。
只需使用"JavaScript Requests" in the documentation 下提到的csrf_token() 函数即可。然后应将令牌添加到“X-CSRFToken”下的请求标头中。


不能在 jinja2 模板之外使用命令csrf_token()。 如果可能,将令牌作为参数传递,或者使用描述的 html 元标记。

将此添加到您的模板中。

<meta content="{{ csrf_token() }}" name="csrf-token">

现在在您的脚本文件中使用它。

const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");

【讨论】:

  • 感谢您的回复。我尝试在 javascript 代码中设置 csrf_token 并编辑了问题,但没有成功。你能读一下我编辑的部分吗?谢谢
  • 感谢您的更新。您的指导绝对帮助了我并解决了问题!
猜你喜欢
  • 2022-01-27
  • 2021-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-03
  • 2022-08-19
  • 1970-01-01
  • 2017-03-10
相关资源
最近更新 更多