CSRF 防护
django.contrib.csrf
开发包能够防止遭受跨站请求伪造攻?(CSRF).
CSRF, 又叫会话跳转,是一种网站安全攻击技术?当某个恶意网站在用户未察觉的情况下将其从一个已经通过身份验证的站点诱骗至一个新?URL 时,这种攻击就发生了,因此它可以利用用户已经通过身份验证的状态?乍一看,要理解这种攻击技术比较困难,因此我们在本节将使用两个例子来说明
一个简单的 CSRF 例子
假定你已经登录到 example.com
的网页邮件账号。该网站有一个指?tt class="docutils literal">example.com/logout的注销按钮。就是说,注销其实就是访问example.com/logout
通过在(恶意)网页上用隐藏一个指?URL example.com/logout
?<iframe>
,恶意网站可以强迫你访问该 URL 。因此,如果你登?example.com
的网页邮件账号之后,访问了带有指?example.com/logout
?<iframe>
的恶意站点,访问该恶意页面的动作将使你登?example.com
?Thus, if you’re logged in to the example.com
webmail account and visit the malicious page that has an <iframe>
to example.com/logout
, the act of visiting the malicious page will log you out from example.com
.
很明显,登出一个邮件网站也不是什么严重的安全问题。但是同样的攻击可能针对任何相信用户的站点,比如在线银行和电子商务网站。这样的话可能在用户不知情的情况下就下订单付款了
稍微复杂一点的CSRF例子
在上一个例子中?example.com
应该负部分责任,因为它允许通过 HTTP GET
方法进行状态变更(即登入和登出)?如果对服务器的状态变更要求使?HTTP POST
方法,情况就好得多了?但是,即便是强制要求使用 POST
方法进行状态变更操作也易受?CSRF 攻击
假设 example.com
对登出功能进行了升级,登?<form>
按钮是通过一个指?URL example.com/logout
?POST
动作完成,同时在 <form>
中加入了以下隐藏的字段:
<input type="hidden" name="confirm" value="true">
这就确保了用简单的指向example.com/logout
?tt class="docutils literal">POST 不会让用户登出;要让用户登出,用户必须通过 POST
?example.com/logout
发送请? 并且发送一个值为’true’的POST变量?confirm
尽管增加了额外的安全机制,这种设计仍然会遭到 CSRF 的攻击——恶意页面仅需一点点改进而已?攻击者可以针对你的站点设计整个表单,并将其藏身于一个不可见?<iframe>
中,然后使用 Javascript 自动提交该表单
防止 CSRF
那么,是否可以让站点免受这种攻击呢? 第一步,首先确保所?GET
方法没有副作用?这样以来,如果某个恶意站点将你的页面包含?<iframe>
,它将不会产生负面效果
该技术没有考虑 POST
请求?第二步就是给所?POST
的form标签一个隐藏字段,它的值是保密的并根据用户进程?ID 生成?这样,从服务器端访问表单时,可以检查该保密的字段。不吻合时可以引发一个错误
这正?Django CSRF 防护层完成的工作,正如下面的小节所介绍的
使用CSRF中间
django.contrib.csrf
开发包只有一个模块: middleware.py
。该模块包含了一?Django 中间件类—?CsrfMiddleware
,该类实现了 CSRF 防护功能
在设置文件中?'django.contrib.csrf.middleware.CsrfMiddleware'
添加?MIDDLEWARECLASSES
设置中可激?CSRF 防护?该中间件必须?SessionMiddleware
之后 执行,因此在列表?CsrfMiddleware
必须出现?SessionMiddleware
之前_ (因为响应中间件是自后向前执行的)?同时,它也必须在响应被压缩或解压之前对响应结果进行处理,因此 CsrfMiddleware
必须?GZipMiddleware
之后执行。一旦将它添加到MIDDLEWARE_CLASSES
设置中,你就完成了工作?参见第十五章的“MIDDLEWARE_CLASSES顺序”小节以了解更多
如果感兴趣的话,下面?CsrfMiddleware
的工作模式?它完成以下两项工作:
它修改当前处理的请求,向所有的
POST
表单增添一个隐藏的表单字段,使用名称是csrfmiddlewaretoken
,值为当前会话 ID 加上一个密钥的散列值?如果未设置会?ID ,该中间件将 不会 修改响应结果,因此对于未使用会话的请求来说性能损失是可以忽略的对于所有含会话 cookie 集合的传?
POST
请求,它将检查是否存?csrfmiddlewaretoken
及其是否正确?如果不是的话,用户将会收到一?403HTTP
错误?403 错误页面的内容是检测到了跨域请求伪装?终止请求
该步骤确保只有源自你的站点的表单才能将数?POST 回来
该中间件特意只针?HTTP POST
请求(以及对应的 POST 表单)?如我们所解释的,永远不应该因为使用了 GET
请求而产生负面效应,你必须自己来确保这一点
未使用会?cookie ?POST
请求无法受到保护,但它们也不 需 受到保护,因为恶意网站可用任意方法来制造这种请求
为了避免转换?HTML 请求,中间件在编辑响应结果之前对它的 Content-Type
头标进行检查?只有标记?text/html
?application/xml+xhtml
的页面才会被修改
CSRF中间件的局限
CsrfMiddleware
的运行需?Django 的会话框架?(参阅第 14 章了解更多关于会话的内容。)如果你使用了自定义会话或者身份验证框架手动管理会?cookies,该中间件将帮不上你的忙
如果你的应用程序以某种非常规的方法创?HTML 页面(例如:?Javascript ?tt class="docutils literal">document.write语句中发?HTML 片段),你可能会绕开了向表单添加隐藏字段的过滤器?在此情况下,表单提交永远无法成功?(这是因为在页面发送到客户端之前,CsrfMiddleware
使用正则表达式来添加csrfmiddlewaretoken
字段到你的HTML中,而正则表达式不能处理不规范的HTML。)如果你怀疑出现了这样的问题。使用你浏览器的查看源代码功能以确定csrfmiddlewaretoken
是否插入到了表单中
想了解更多关?CSRF 的信息和例子的话,可以访?http://en.wikipedia.org/wiki/CSRF