我爱编程

Django 随笔: CSRF 防御

2018-03-16  本文已影响0人  学以致用123

什么是 CSRF?

维基百科定义:https://en.wikipedia.org/wiki/Cross-site_request_forgery

CSRF跨站点请求伪造(Cross—Site Request Forgery),为了便于理解,我们引用http://blog.csdn.net/stpeace/article/details/53512283上的例子进行说明。

例子中涉及以下三个角色:

Web A : 存在安全漏洞的网站;

Web B :进行 CSRF 攻击的恶意网站;

User C: Web A 的网站的授权用户。

CSRF 攻击过程

  1. User C 在浏览器中打开 Web A,并输入用户名和密码进行登录;

  2. Web A 对 User C 的登录信息进行验证并通过后,Web A 生成 cookie 信息并返回给浏览器,User C 登录成功,可以向 Web A 发送请求;

  3. User C 在没有退出 Web A 的情况下,在同一个浏览器中,打开了一个访问 Web B 的页面;

  4. Web B 在 User C 不知情的情况下向 Web A 发送请求(在请求头存放浏览器中 User C 的 Cookie 信息);

  5. Web A 根据请求中的 Cookie 信息认为该请求是 User C 发送的,因此以 User C 的权限处理 Web B 发送请求。

这样 Web B 可以通过 User C 的权限对 Web A 进行操作。

如果 web A 为银行网站,Web B 可能通过这些权限在 User C 不知情的情况下转走 User C 账户中的资金。


Django CSRF 解决方案


Django 1.11 版本官方文档对此有比较全面的介绍,文档信息为:

原文地址:https://docs.djangoproject.com/en/1.11/ref/csrf/

翻译地址:https://www.jianshu.com/p/2b6c69f2d520


Django 什么时候进行 CSRF 防御?

Django 为使用 django-admin startproject 命令创建的项目自动设置 csrfviewmiddleware 后端进行 CSRF 防御。

csrfviewmiddleware 会对 POST 、PUT 和 DELETE 等请求进行检查,如果这些请求不包含 csrf token 或者 包含的 csrf token 不正确,会返回 403 Forbidden 响应。

解决 403 Forbidden 的简单方法

这是我们开始 Django 开发时经常遇到的一个问题。解决 '403 Forbidden' 最简单的一个方法是取消 Django 的 csrf 防御设置,我们可以采用以下两种方法实现:

  1. 打开 settings.py 文件,注释掉或者删除 MIDDLEWARE 设置中的 'django.middleware.csrf.CsrfViewMiddleware'。

  2. 为处理 POST 请求的视图添加 csrf_exempt 装饰器。

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
​
@csrf_exempt
def my_view(request):
 return HttpResponse('Hello world')</pre>

这两种方法可以解决 '403 Forbidden' 的问题,但是却禁用了 csrfviewmiddleware 后端,网站遇到 CSRF 攻击将无法进行防御。

因此,我们需要做的是想办法通过 csrfviewmiddleware 后端的检查。

正确的方法

csrfviewmiddleware 对 POST 、PUT 和 DELETE 等请求进行以下检查:

  1. cookie 和 POST ( PUT 或 DELETE ) 数据中必须包含 csrf token 信息;

  2. cookie 和 POST ( PUT 或 DELETE ) 数据中的 csrf token 必须一致。

如果我们能满足以上两个要求,即可通过 csrfviewmiddleware 的验证。

下面,我们解决 Django 开发中最常用的 表单 POST 请求和 AJAX POST 请求的设置方法。


Form 表单

只需要表单模板的 <form> 元素中加入 csrf_token 模板标签即可:

<form action="" method="post">
 {% csrf_token %}

然后使用 render() 函数或者通用视图渲染表单模板即可。后台会自动把 {% csrf_token %} 渲染为

<input type='hidden' name='csrfmiddlewaretoken' value='****' />

这样 POST 的数据中将会通过名为 csrfmiddlewaretoken 的键值对包含 csrf token 的信息,与此同时,后台还会在 cookie 中添加 csrf_token。

这样,即可通过 csrfviewmiddleware 的检验需求。


AJAX

AJAX 可以采用以下三种方法来处理。

方法 1

一劳永逸的方法,使用 Django 提供的方法:为满足条件的 XMLHttpRequest 设置一个自定义 X-CSRFToken标头保存 CSRF token 。由于许多 JavaScript 框架提供为每个请求设置标头的 hooks,这样做会很简单。

 function csrfSafeMethod(method) {
 // these HTTP methods do not require CSRF protection
 return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
 }
​
 $.ajaxSetup({
 beforeSend: function (xhr, settings) {
 if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
 xhr.setRequestHeader('X-CSRFToken', '{{ csrf_token }}');
 }
 }
 });

这里设置了添加 csrf_token 的前提条件,要求满足 !csrfSafeMethod(settings.type) && !this.crossDomain 才可以添加,这样做是为了避免 csrf_token 泄露。

注意,这里使用的是 {{ csrf_token }},Django 将其渲染为 token 的值,并在 cookie 中添加 csrf_token。这样也可以满足 POST 数据和 cookie 中包含相同的值。

方法2

在 ajaxSetup 的 data 中设置 csrf_token,这样会为 ajax post( put、delete )请求的 data 添加 csrf_token:

 function csrfSafeMethod(method) {
 // these HTTP methods do not require CSRF protection
 return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
 } 

 $.ajaxSetup({
 beforeSend: function (xhr, settings) {
 if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
 data:{csrfmiddlewaretoken:'{{ csrf_token }}'}
 }
 }
 });

在一般情况下, 方法2 等效于方法1,但是如果 ajax post 传输序列化数据,比如:

$.post('/', $('form').serialize())

jQuery 不能将序列化数据与ajaxSetup data 中的数据进行合并,从而造成 ajax 的 POST 数据中不包含 csrf_token。

方法3

为每个 POST 请求的 ajax 的 data 中添加 csrf_token ,也就是:

$.ajax({
 url:"...",
 data:{"csrfmiddlewaretoken":'{{ csrf_token }}','other_key':'value'}
 type:"POST",
 success:function (data) {
 alert(data)
 }
 })
 })

这种方法简单,但是如果存在多个 ajax POST 请求,则比较麻烦。


csrf_token 模板标签如何正常工作

前面,我们使用了{% csrf_token %} 和 {{ csrf_token }} 模板标签。要保证 csrf_token 正常工作,需要启动 csrfviewmiddleware 后端 或者设置 csrf_protect 装饰器。如果这两者都没运行,则要使用 requests_csrf_token 装饰器。

强烈推荐使用上面的模板标签获取 csrf_token。如果不采用这个方法,在 AJAX 中,还可以根据 CSRF_USE_SESSION 的值的不同采用下面的方法从 cookie 或者 session获取 csrf_token。

从 cookie 中获取 csrf_token

<script src=" http://cdn.jsdelivr.net/jquery.cookie/1.4.1/jquery.cookie.min.js "></script>
<script>
 var csrftoken = $.cookie('csrftoken');

这些代码从一个公共CDN加载jQuery cookie插件,这样我们可以与cookie交互,然后使用插件读取csrf token 的值。然后我们可以用 csrftoken 代替 {{ csrf_token }}。

从 session 中获取 csrf_token

我们可以这样读取 csrf_token:

{% csrf_token %}
<script type="text/javascript">
// using jQuery
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
</script>

这种方法的前提是 HTML中必须包含 CSRF token。

上一篇下一篇

猜你喜欢

热点阅读