Django 随笔: CSRF 防御

什么是 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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容