CSRF 令牌未被验证

CSRF token is not being verified

我有一个表格:

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

我可以做些什么来强制执行它的验证?

@dabadaba 也许 CSRF 令牌是在 cookie 中发送的。所以无论你做什么 post cookies 都会被发送到服务器。阅读此处了解更多详情:https://docs.djangoproject.com/en/2.1/ref/csrf/

简介

OWASP cheatsheet.

中详细解释了多种 CSRF 保护模式

Django CSRF 保护

Django 使用双重提交 Cookie 模式。它易于实现(与验证码等 interaction-based 模式相比),更重要的是:它是 RESTful API 等无状态服务的推荐模式。

它是如何工作的?

假设敏感服务,因为该服务在 CSRF 攻击方面存在漏洞,而您希望使其安全。

客户端在调用敏感服务之前通过以下方式了解 CSRF 令牌:

  • 在 server-side 呈现的 HTML 文件中 HTML 注入值
  • 在以前的名为 CSRF-token 的服务中发送给客户端的 cookie 令牌是 secure-random,与身份验证令牌不同。

所以客户端必须在两个字段中包含CSRF token(这样才叫double submit cookie),举两个例子:

  1. 以隐藏输入字段和 CSRF cookie 两种形式提交 header
  2. X-CSRF-TOKEN 自定义 header 和 CSRF cookie header
  3. 中提交

因此 Django CSRF 中间件将只检查两个令牌是否已提交以及是否相等。

Note that it is assumed that the hacker is not able to run javascript code at the client's browser to submit the values. This vulnerability must be defended in terms of XSS attack prevention.

要检查您是否可以实现 Django 框架的解决方案,请检查文档 here。特别检查这些部分:

对于无状态服务:

@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

对于有状态服务(例如 server-side 呈现的表单):

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

警告!

在 Django 设置文件中启用 django.middleware.csrf.CsrfViewMiddleware 对于无状态方法来说是不够的。您还必须设置 @csrf_protect 装饰器。所以不要仅仅满足于启用中间件。我不知道为什么很棒的 Django 家伙没有在 this documentation version 中明确提到这一点,因为它会在没有任何保护的情况下导致性能开销,我认为这是混淆的根源。不管怎样!

您可以测试并确保使用如下代码完成保护:
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 在评论中首先指出此注释。