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