【问题标题】:Why does Django/Django REST Framework not validate CSRF tokens in-depth, even with enforce-CSRF?为什么 Django/Django REST Framework 不深入验证 CSRF 令牌,即使使用强制 CSRF?
【发布时间】:2021-01-11 13:42:20
【问题描述】:

我正在尝试为对匿名用户开放的 Django Rest API 强制执行 CSRF。 为此,我尝试了两种不同的方法:

  1. 从一个 CSRFAPIView 基本视图扩展选定的 API 视图,该基本视图在调度方法上有一个 @ensure_csrf_cookie 注释。
  2. 使用基于 SessionAuthentication 的自定义 Authentication 类,无论用户是否登录,它都会应用 enforce_csrf()

在这两种方法中,CSRF 检查似乎都在表面上起作用。如果 cookie 中缺少 CSRF 令牌或令牌的长度不正确,端点将返回 403 - Forbidden。 但是,如果我在 cookie 中编辑 CSRF 令牌的值,则请求会被毫无问题地接受。所以我可以为 CSRF 使用随机值,只要它的长度正确。

这种行为似乎偏离了常规的 Django 登录视图,其中 CSRF 的内容确实很重要。我正在使用 debug/test_environment 标志在本地设置中进行测试。

我在 DRF 中的自定义 CSRF 检查未得到深入验证的原因可能是什么?

自定义认证的代码片段:

class RestCsrfAuthentication(SessionAuthentication):
    def authenticate(self, request):
        self.enforce_csrf(request)
        rotate_token(request)
        return None

在设置中:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'csrfexample.authentication.RestCsrfAuthentication',
    ]
}

【问题讨论】:

  • 快速检查一下:您是否尝试禁用 debug/test_env 标志?
  • 我也在非调试环境下试过了。我已经将它与调试模式下的其他 CSRF 行为(登录页面)进行了比较,并且登录页面上的行为更加严格。
  • 你能分享这两种方法的实现细节吗? (对于#2,我假设您正在覆盖SessionAuthentication.authenticate()?)无论使用哪种HTTP 方法,都会发生这种情况吗?你使用的是什么 Django 和 DRF 版本?
  • @DJRamones 我正在使用 Django 3.0.10,DRF 3.11.2

标签: django django-rest-framework csrf


【解决方案1】:

实际上,Django 中 CSRF 令牌的具体内容无关紧要。

This reply by a Django security team memberquestion similar to yours 说:

我们的 CSRF 令牌的工作方式非常简单。每个表单都包含一个与 CSRF cookie 匹配的 CSRF 令牌。在处理受保护的表单之前,我们确保提交的令牌与 cookie 匹配。这是一个服务器端检查,但它不针对存储的服务器端值进行验证。由于远程攻击者应该无法读取或设置您域上的任意 cookie,因此可以保护您。

由于我们只是将 cookie 与发布的令牌进行匹配,因此数据并不敏感(事实上它完全是任意的 - “zzzz”的 cookie 就可以了),因此轮换/过期建议不会产生任何区别。如果攻击者可以在您的域上读取或设置任意 cookie,则所有形式的基于 cookie 的 CSRF 保护都会被破坏,句号。

(实际上,由于长度要求,“zzzz”不起作用,但稍后会详细介绍。)我建议阅读整个邮件列表消息以获得更全面的理解。那里有关于 Django 在框架中的独特之处的解释,因为 CSRF 保护独立于会话。

我通过this FAQ item on the Django docs找到了邮件列表消息:

发布任意 CSRF 令牌对(cookie 和 POST 数据)是否存在漏洞?

不,这是设计使然。如果没有中间人攻击,攻击者就无法向受害者的浏览器发送 CSRF 令牌 cookie,因此成功的攻击需要通过 XSS 或类似方式获取受害者浏览器的 cookie,在这种情况下攻击者通常不需要 CSRF 攻击。

一些安全审计工具将此标记为问题,但如前所述,攻击者无法窃取用户浏览器的 CSRF cookie。 使用 Firebug、Chrome 开发工具等“窃取”或修改您自己的令牌不是漏洞。

(强调我的。)

该消息来自 2011 年,但它仍然有效,为了证明这一点,让我们看一下代码。 Django REST Framework 的 SessionAuthenticationensure_csrf_cookie 装饰器都使用核心 Django 的 CsrfViewMiddleware (source)。在该中间件类的 process_view() method 中,您会看到它获取 CSRF cookie(默认情况下名为 csrftoken 的 cookie),然后是已发布的 CSRF 令牌(已发布数据的一部分,回退到读取 @987654335 @标头)。之后,它在 POSTed/X-CSRFToken 值上运行 _sanitize_token()。此清理步骤是检查正确令牌长度的地方;这就是为什么当您提供更短或更长的令牌时会按预期获得 403。

之后,该方法继续使用the function _compare_salted_tokens() 比较两个值。如果您阅读该函数以及它进行的所有进一步调用,您会发现它归结为检查两个字符串是否匹配,基本上不考虑字符串的值。 p>

这种行为似乎偏离了常规的 Django 登录视图,其中 CSRF 的内容确实很重要。

不,即使在内置登录视图中也没关系。我针对一个大多数默认的 Django 项目运行了这个 curl 命令(Windows cmd 格式):

curl -v
  -H "Cookie: csrftoken=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl"
  -H "X-CSRFToken: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl"
  -F "username=admin" -F "password=1234" http://localhost:8000/admin/login/

然后 Django 返回了一个会话 cookie(当然还有一个 CSRF cookie)。


请注意您覆盖SessionAuthentication.authenticate() 的方式:您可能已经知道这一点,但是according to the DRF docs 如果请求具有会话数据,则该方法应该返回(User, auth) 元组而不是None,即如果该请求来自登录用户。另外,我认为rotate_token() 是不必要的,因为这段代码只检查身份验证状态,并不关心实际验证用户。 (Django sourcerotate_token() “应该在登录时完成”。)

【讨论】:

    猜你喜欢
    • 2020-09-12
    • 2016-09-09
    • 1970-01-01
    • 2019-02-26
    • 2021-11-27
    • 2019-09-15
    • 1970-01-01
    • 2015-02-11
    • 2013-05-06
    相关资源
    最近更新 更多