从不涉及 Flask 表单的 PUT 获取请求中获取 CSRF 错误

getting CSRF errors from PUT fetch request that does not involve Flask forms

我在我的 Flask web 应用程序中收到来自常规 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 令牌 有一个使用 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 调试器 -> 应用程序 -> cookies -> https://localhost cookie 我只看到 session 令牌,但看不到 csrf_token

问题:


编辑 1:

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

我知道如果这段代码嵌入到 html 页面中,那么 jinja2 将从函数 csrf_token() 返回的值替换为变量 csrf_token

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

我对 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 的指导:

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

这解决了问题。
我没有看到 CSRF 错误。
我还在 chrome-devtools -> 网络中检查了失败的请求,我确实在请求 header x-csrf-token 中看到了预期的结果。

x-csrf-token: ImI0ODY...

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

CSRF 令牌不是在您的 Flask 应用程序初始化期间生成的。它更依赖于请求。关键是,攻击者可以接管其他人的 session 并代表他们发送请求。据我所知,它应该是每个请求的令牌,并确保对方客户端仍然是发出前一个 GET 请求的客户端。另请参阅 this
关于何时以及如何根据 ajax 请求更新 CSRF 令牌,存在各种讨论。我不是安全专家,现在不想参与其中。

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


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

将此添加到您的模板中。

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

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

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