Niffler

X2.17.16340232-解决 Django 的 CSRF 保护引起的 403 FORBIDDEN

Posted By 16340232

什么是 CSRF

CSRF,(Cross-site request forgery),跨站请求伪造,指通过伪装来自受信任用户的请求来进行对受信任的网站一些操作。

csrf

从上图可以看出,要完成一次 CSRF 攻击,受害者必须依次完成两个步骤:

1.登录受信任网银网站,并在本地生成 Cookie。 2.在不登出网银网站的情况下,访问危险网站钓鱼网站。 这时候受害者就中招了。

  • CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以随机产生一个 token 放到 HTTP 头中自定义的属性里。并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
  • token 可以在用户登陆后产生并放于 session 之中,然后通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 server 产生的 token 值放入其中。然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对。

Django 的 CSRF 保护机制

一种防范方法是在 HTTP 头中自定义属性并验证:

HTTP 请求,一般分为两类:“安全请求”和“不安全请求”。GET 是“安全请求”, POST, PUT, DELETE 是“不安全请求”。

对于“不安全请求”,Django 设计了 CSRF 验证,简单来说这个机制是这样的:

(1) 客户端访问 Django 站点,Django 服务器向客户端发送名为 ”csrftoken” 的 cookie

(2) 客户端对 Django 服务器发送不安全请求时,必须在 HTTP 头部加入 ”X-CSRFToken”字段,并将这个 cookie 的值作为该字段的值

(3) Django 服务器端会对 HTTP 头部 X-CSRFToken 的值进行验证,依次来判断这个请求是不是来自合法用户


解决 403 FORBIDDEN

在使用 postman 测试 API 时候,对于 POST 请求,返回如下错误:

{"detail":"CSRF Failed: CSRF token missing or incorrect."}

原则上,只要在 POST 的 HTTP 头部加入 ”X-CSRFToken” 字段就可以。但实际测试发现,即便加了,也会得到 403 错误,而且,cookie 中的 “csrftoken” 有时候能获取到,有时候获取不到。

在浏览器的 Network 中查看报文发现,client 在登录后,server response header 中无 ”Set-Cookie” 字段,导致再次访问时 server 无法根据 cookie 判断当前用户,只能得到匿名用户。

原因应该又是跨源访问造成的,client 和 server 不同源(CORS 可以见 X2.16 报告),所以服务器发送的 HTTP 响应,是不能设置浏览器 cookie 的。

所以安装 django-cors-header,并在 setting 中加入

CORS_ALLOW_CREDENTIALS = True

这样会让服务器同意客户端发送 Cookie

csrf

但有时候因为 domain 的问题 cookie 还是获取不到,所以一种强制的方法是继承重写 Django 的 SessionAuthentication 类,使其跳过 csrf 检测:

class CsrfExemptSessionAuthentication(SessionAuthentication):

    def enforce_csrf(self, request):
        return  # To not perform the csrf check previously happening

然后在 APIView 类中使用

authentication_classes = (CsrfExemptSessionAuthentication,)