为什么Django/Django REST框架不深入验证CSRF令牌,即使使用强制CSRF?

为什么Django/Django REST框架不深入验证CSRF令牌,即使使用强制CSRF?,django,django-rest-framework,csrf,Django,Django Rest Framework,Csrf,我试图为Django Rest API强制执行CSRF,该API对匿名用户开放。 为此,我尝试了两种不同的方法: 从一个CSRFAPIViewbase视图扩展选定的API视图,该视图在分派方法上具有@sure\u csrf\u cookie注释 使用基于SessionAuthentication的自定义身份验证类,该类应用于enforce\u csrf(),无论用户是否登录 在这两种方法中,CSRF检查似乎表面上起作用。如果cookie中缺少CSRF令牌或令牌的长度不正确,端点将返回一个403-

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

  • 从一个
    CSRFAPIView
    base视图扩展选定的API视图,该视图在分派方法上具有
    @sure\u csrf\u cookie
    注释
  • 使用基于SessionAuthentication的自定义身份验证类,该类应用于
    enforce\u csrf()
    ,无论用户是否登录
  • 在这两种方法中,CSRF检查似乎表面上起作用。如果cookie中缺少CSRF令牌或令牌的长度不正确,端点将返回一个
    403-禁止的
    。 但是,如果我在cookie中编辑CSRF令牌的值,则请求将被接受而不会出现问题。所以我可以使用CSRF的随机值,只要长度正确

    这种行为似乎偏离了常规的Django登录视图,在该视图中,CSRF的内容确实很重要。我正在本地设置中测试,调试/测试_环境标志处于打开状态

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

    自定义身份验证的代码片段:

    类RestCsrfAuthentication(会话身份验证):
    def身份验证(自我、请求):
    自我强制执行csrf(请求)
    轮换令牌(请求)
    一无所获
    
    在设置中:

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

    实际上,Django中CSRF令牌的具体内容并不重要

    对一位女士说:

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

    因为我们只是将cookie与发布的令牌进行匹配,所以数据并不敏感(事实上,它完全是任意的——一个“zzzz”cookie工作得很好),所以轮换/过期建议没有任何区别。如果攻击者可以在您的域上读取或设置任意cookie,则所有形式的基于cookie的CSRF保护都将被破坏,完全停止

    (实际上“zzzz”不起作用,因为长度要求,但稍后会有更多内容。)我建议阅读完整的邮件列表信息,以便更全面地理解。有人解释了Django在框架中的特殊性,因为CSRF保护独立于会话

    我通过以下途径找到该邮件列表消息:

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

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

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

    (我的重点。)

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

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

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

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

    curl-v
    -H“Cookie:csrftoken=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvxyzabcdefghijkl”
    -H“X-CSRFToken:ABCDEFGHIJKLMNOPQRSTUVWXYZABCDFGHIJKLMNOPQRSTUVWXYZABCDFGHIJKL”
    -F“用户名=管理员”-F“密码=1234”http://localhost:8000/admin/login/
    
    Django返回了一个会话cookie(当然还有一个CSRF cookie)



    请注意重写会话身份验证。authenticate():您可能已经知道这一点,但是如果请求包含会话数据,即请求来自登录用户,则该方法应该返回
    (User,auth)
    元组,而不是
    None
    。另外,我认为
    rotate\u token()
    是不必要的,因为这段代码只检查身份验证状态,而与实际验证用户无关。(Django说,
    rotate_token()
    “应该在登录时完成”。

    只是一个快速检查:您是否尝试禁用debug/test_env标志?我也在非调试环境中尝试过。我已经将它与调试模式下的其他CSRF行为(登录页面)进行了比较,并且在登录页面上的行为更为严格。您能分享这两种方法的实现细节吗?(使用#2,我假定您正在覆盖
    会话身份验证.authenticate()
    ?)