CSRF令牌未被验证。

3

我有一个表单:

<form action="{% url "some_url" %}" method="post">
    {% csrf_token %}
    <input type="text">Text Input
    <button type="submit">Submit</button>
</form>

通过 AJAX 提交的内容:
$(function () {
    $('form').submit(function (e) {
        e.preventDefault();
        $.post($(this).attr('action'), $(this).serialize(), function (response) {
            console.log(response);
        });
    });
});

该URL路由到此视图:
class SomeView(View):
    def post(self, request, *args, **kwargs):
        context = dict(**some_data)
        rendered_html = render_to_string('some_template.html', context, RequestContext(self.request))
        return JsonResponse(dict(html=rendered_html))

所有这些都有效。问题在于当CSRF令牌未发送时,它也可以正常工作,我收到了完全相同的成功响应:
$.post($(this).attr('action'), function (response) {
    console.log(response);
});

我希望在缺少CSRF令牌时会出现某种错误。

显而易见的是:CsrfViewMiddlewareMIDDLEWARE_CLASSES中。

当没有发送令牌时,明确使用csrf_token会产生相同的结果:

method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
    return super(SomeView, self).dispatch(request, *args, **kwargs)

我该如何强制执行它的验证?


你是否有自定义的 MIDDLEWARE 设置?请确认 django.middleware.csrf.CsrfViewMiddleware 是否已启用。https://docs.djangoproject.com/en/2.1/ref/csrf/#how-to-use-it - Håken Lid
@HåkenLid,就像我说的那样,它在MIDDLEWARE_CLASSES中。有没有办法在运行时检查它是否确实处于活动状态,以确保它正在工作?(如果一开始它没有激活,我相信{% csrf_token %}会输出一个空字符串,但我得到了一个实际的令牌。) - dabadaba
1
@dabadaba 也许CSRF令牌是通过cookie发送的。因此,无论您发布什么内容,cookie都将被发送到服务器。在此处阅读更多详细信息:https://docs.djangoproject.com/en/2.1/ref/csrf/ - norbert.mate
好的,令牌已经通过 cookie 发送了。你们可以将其发布为答案,我会接受它。感谢你们的帮助! - dabadaba
Cookie 只是其中的一部分。在 CsrfViewMiddleware 允许响应之前,它还必须以表单数据或 X-CsrfToken 标头的形式发送。如果您使用 curl -X POST <url> 访问 URL,它是否仍然有效? - knbk
显示剩余6条评论
2个回答

0

简介

OWASP cheatsheet 中详细解释了各种 CSRF 保护模式。

Django CSRF 保护

Django 使用双重提交 cookie 模式。与基于交互的模式(如 Captcha)相比,它更容易实现,并且更为重要的是:对于像 RESTful API 这样的无状态服务,这是推荐的模式。

它是如何工作的呢?

假设 敏感服务 存在 CSRF 攻击漏洞,您想要使其安全。

客户端在调用敏感服务之前会得到 CSRF token,方法有两种:

  • 通过服务器端渲染 HTML 文件注入的值
  • 通过前面的服务发送给客户端的名为 CSRF-token 的 cookie。该令牌是安全随机生成的,而且不同于身份验证令牌。

因此,客户端必须将 CSRF 令牌包含在两个字段中(因此称为双重提交 cookie),以下是两个例子:

  1. 将其提交到隐藏的输入字段和CSRF cookie头中
  2. 将其提交到X-CSRF-TOKEN自定义头和CSRF cookie头中

因此,Django CSRF中间件将仅检查是否提交了两个令牌并且是否相等。

请注意,假定黑客无法在客户端浏览器上运行javascript代码以提交值。必须从XSS攻击预防的角度进行防御。

要检查您可以实现Django框架的解决方案,请查看此处的文档。特别是检查以下部分:

对于无状态服务:

@ensure_csrf_cookie
def the_previous_called_service_sending_the_csrf_cookie(request):
    # the implementation
    return response

@csrf_protect
def the_sensitive_service(request):
    # the implementation
    return response

对于有状态服务(例如服务器端渲染表单):

<form method="post">
    {% csrf_token %}
    <!--- other form fields --->
</form>

警告!

在 Django 设置文件中启用 django.middleware.csrf.CsrfViewMiddleware 并不足以实现无状态方法。您还必须设置 @csrf_protect 装饰器。因此,不要仅仅满足于启用中间件。我不知道为什么这篇文档中的 Django 专家没有明确提及这一点,这会导致性能开销而没有任何保护,我认为这是一个混淆源。总之!

您可以使用以下代码测试和确保已完成保护:
def test_sensitive_service_should_fail_without_csrf_header(self):
    csrf_client = APIClient(enforce_csrf_checks=True,)
    csrf_client.force_authenticate(self.user1)
        response = csrf_client.post("path/to/sensitive/service",
        format="json",
    )
    self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)


def test_sensitive_service_should_succeed_with_csrf_header(self):

    csrf_client = APIClient(enforce_csrf_checks=True)
    csrf_client.force_authenticate(self.user1)
    data = {"my_field": "my_value"}
    # service to get csrf token before calling sensitive service
    auth_response = self.client.post(
        "/path/to/prior/service", data=data, format="json", follow=True
    )
    csrf_client.cookies = auth_response.cookies
    csrf_token = auth_response.cookies["csrftoken"]
    # calling sensitive service with appropriate csrf token header
    response = csrf_client.put("/path/to/sensitive/service/",
        format="json",
        HTTP_X_CSRFTOKEN=csrf_token.value,
        secure=True,
    )
    self.assertEqual(response.status_code, HTTP_200_OK)

此外,我要感谢@knbk在评论中首先指出了这个注释。


0

Django使用“双重提交Cookie模式”(Double Submit Cookie pattern)来防止CSRF攻击。仅仅在cookie中发送CSRF令牌是不足以绕过CsrfViewMiddleware的。相同的令牌需要同时存在于cookie和POST请求体或“X-CsrfToken”头中。 - knbk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接