跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
假如一家银行用以运行转账操作的URL地址如下:http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码:
如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。
HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于www.examplebank.com之下。而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于www.examplebank.com之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer字段。虽然http协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其Referer字段的可能。
由于CSRF的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在cookie中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行CSRF攻击。这种数据通常是窗体中的一个数据项。服务器将其生成并附加在窗体中,其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过CSRF传来的欺骗性攻击中,攻击者无从事先得知这个伪随机数的值,服务端就会因为校验token的值为空或者错误,拒绝这个可疑请求。
CSRF 攻击让恶意用户可以使用别的用户的证书执行操作,且是在其不知情或不同意的情况下。
Django 已经内置了保护措施来对抗大多数 CSRF 攻击。CSRF 中间件和模板标签提供了易于使用的保护,防止跨站请求伪造。 当一个恶意网站包含一个链接、一个表单按钮或一些 JavaScript,目的是在你的网站上执行一些操作,使用在浏览器中访问恶意网站的登录用户的凭证时,就会发生这种类型的攻击。 此外,还包括一种相关的攻击类型,“登录 CSRF”,即攻击网站欺骗用户的浏览器使用他人的凭证登录网站。但和多数缓解性技术一样,它是有局限性的。比如可以全局禁用 CSRF 模块或者特定的视图。如果您真的想这么做,请三思而后行。
CSRF 保护机制通过检查每一个 POST 请求中的密文来实现。这保证恶意用户不能“复现”一个表单并用 POST 提交到你的网页,并让一个已登录用户无意中提交该表单。恶意用户必须知道特定于用户的密文(使用cookie)。
在部署HTTPS时,CsrfViewMiddleware
会检查 HTTP 报文的 referer 首部是否设置为同源的 URL(包括子域和端口)。因为 HTTPS 提供了额外的安全性,所有通过转发不安全连接请求并在支持的浏览器中使用 HSTS 来确保连接在可用的地方使用了 HTTPS ,这一点是很重要的。
对 CSRF 攻击的第一道防线是确保 GET 请求没有副作用。通过“不安全”方法的请求,如 POST、PUT 和 DELETE,则可以通过以下步骤来保护。
要在你的视图中利用 CSRF 保护,请遵循以下步骤:
CSRF 中间件默认在MIDDLEWARE
配置中被激活。如果你覆盖了这个配置,请记住 'django.middleware.csrf.CsrfViewMiddleware'
应该排在任何假设 CSRF 攻击已经被处理的视图中间件之前。如果你禁用了它,这并不推荐,你可以使用csrf_protect()
]对你想要保护的特定视图进行保护(见下文)。
在任何使用 POST 表单的模板中,如果表单是针对内部 URL 的,请在 元素中使用
csrf_token
标签,例如:
<form method="post">
{% csrf_token %}
form>
对于以外部 URL 为目标的 POST 表单,不应该这样做,因为这会导致 CSRF 令牌泄露,从而导致漏洞。
在相应的视图函数中,确保 RequestContext
用于渲染响应,这样 {% csrf_token %}
才能正常工作。如果你使用的是render()
函数、通用视图或 contrib 应用程序,你已经被覆盖了,因为这些都使用 RequestContext
。
但是回想一下,我们之前的注册和登录是将settings中的csrf中间件进行注销的,而且我们在ajax中并没有配置任何的权限认证机制,那么接下来,我们就看看如何在ajax中配置csrf。
虽然上述方法可以用于 AJAX POST 请求,但它有一些不便之处:你必须记住在每个 POST 请求中都要把 CSRF 令牌作为 POST 数据传递进来。出于这个原因,有一种替代方法:在每个 XMLHttpRequest 上,设置一个自定义的 X-CSRFToken
头为 CSRF 标记的值。这通常比较容易,因为许多 JavaScript 框架提供了钩子,允许在每个请求中设置头。
首先,你必须获得 CSRF 令牌。如何做取决于CSRF_USE_SESSIONS
和CSRF_COOKIE_HTTPONLY
配置是否启用。
CSRF_USE_SESSIONS
和 CSRF_COOKIE_HTTPONLY
为 False
时获取令牌推荐的令牌来源是 csrftoken
cookie,如果你已经为你的视图启用了上文所述的 CSRF 保护,则会设置该 cookie。CSRF 令牌 cookie 默认命名为 csrftoken
,但你可以通过 CSRF_COOKIE_NAME
配置来控制 cookie 的名称。你可以通过这样的方式获得令牌:
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
CSRF_USE_SESSIONS
或 CSRF_COOKIE_HTTPONLY
为 True
时获取令牌如果你激活了 CSRF_USE_SESSIONS
或 CSRF_COOKIE_HTTPONLY
,你必须在你的 HTML 中包含 CSRF 令牌,并通过 JavaScript 从 DOM 中读取该令牌:
{% csrf_token %}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
script>
最后,你需要在 AJAX 请求中设置头。使用 fetch() API:
const request = new Request(
/* URL */,
{headers: {'X-CSRFToken': csrftoken}}
);
fetch(request, {
method: 'POST',
mode: 'same-origin' // Do not send CSRF token to another domain.
}).then(function(response) {
// ...
});
我们并没开启 CSRF_USE_SESSIONS
或 CSRF_COOKIE_HTTPONLY
,因此下面的代码中,getcookie方法与上面类似,csrfSafeMethod方法控制http请求方法,而ajaxSetup则是在ajax请求头上添加这个cookie
// get cookie using jQuery
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// Setting the token on the AJAX request
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
因此,我们只需要在之前的登录、注册和修改密码的js最后添加上这三个函数即可。
如果按照ajax的方法,就需要每一次提交表单和类似操作的时候都需要设置一次csrftoken,会显得有些麻烦,因此我们可以使用中间件来帮助我们处理这些。
使用方法
还是先把html中csrf_token
进行删除(注意,js里面的部分不能动,不然会出现错误)
首先:创建一个中间件
# utils/middleware.py
# -*- coding: utf-8 -*-
# @Auther:Summer
from django.middleware.csrf import get_token
from django.utils.deprecation import MiddlewareMixin
class Middleware(MiddlewareMixin):
def process_request(self, request):
get_token(request)
其次:将写好的中间件在settings中进行注册
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utils.middleware.Middleware'
]
最后:重启服务器查看
与其添加 CsrfViewMiddleware
作为全面保护,不如在需要保护的特定视图上使用 csrf_protect
装饰器,它具有完全相同的功能。它必须用于同时 在输出中插入 CSRF 令牌的视图和接受 POST 表单数据的视图。(这些通常是相同的视图函数,但并不总是如此)。
不建议 单独使用装饰器,因为如果忘记使用,就会出现安全漏洞。“腰带和支架”的策略,两者同时使用也可以,而且会产生最小的开销。
使用方法:
from django.views.decorators.csrf import *** # 装饰器来进行csrf验证
from django.utils.decorators import method_decorator # 由于不能直接给类视图进行装饰,需要转化一下
csrf_exempt
(view)
该装饰器标记着一个视图被免除了中间件所确保的保护。
例如:
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
requires_csrf_token
(view)
通常情况下,如果 CsrfViewMiddleware.process_view
或类似 csrf_protect
这样的等价物没有运行, csrf_token
模板标签将无法工作。视图装饰器 requires_csrf_token
可以用来确保模板标签工作。这个装饰器的工作原理与 csrf_protect
类似,但绝不会拒绝接收到的请求。
举例:
from django.shortcuts import render
from django.views.decorators.csrf import requires_csrf_token
@requires_csrf_token
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
ensure_csrf_cookie
(view)
该装饰器强制视图发送 CSRF cookie。不管其是否使用模板标记csrf_token
或者CsrfViewMiddleware
from django.http import HttpResponse
from django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
csrf_protect
(view)
为视图提供 CsrfViewMiddleware
保护的装饰器。
用法:
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
类视图有两种,放在类上面和放在函数上
放在类上面
# @method_decorator(ensure_csrf_cookie) 因为是类视图,在经过url之后需要进行分发,所有需要指定name
# 装饰基于类的视图的每个实例,你需要装饰类定义本身。为此,你可以将装饰器应用到类的 dispatch() 方法。
@method_decorator(ensure_csrf_cookie, name='dispatch')
class RegisterView(View):
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
放在函数上
class RegisterView(View):
@ensure_csrf_cookie
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
一定要小心的是,放置的位置,必须是一整个from表单提交的视图,比如我们项目中注册页面中,registerView是整个视图,而SmScode视图只是主页中的一小部分,因此如果添加在SmScode上就会包403错误,需要放在RegisterView上。
CSRF 保护是基于以下几点:
一个基于随机密钥值的 CSRF cookie,其他网站无法访问。
这个 cookie 是由 CsrfViewMiddleware
设置的。如果在请求中还没有设置的话,那么它将与调用django.middleware.csrf.get_token()
(内部用于获取 CSRF 令牌的函数)的每个响应一起发送。
为了防止BREACH 攻击,令牌不是简单的密钥,而是在密钥前面加上一个随机掩码,用来扰乱密钥。
出于安全考虑,每次用户登录时都会改变密钥的值。
一个隐藏的表单字段,名称为“csrfmiddlewaretoken”,存在于所有发出的 POST 表单中。这个字段的值也是密钥的值,但有一个掩码,这个掩码会被添加到字段中,并被用来扰乱字段。掩码在每次调用 get_token()
时都会重新生成,所以表单字段的值在每次响应时都会改变。
这一部分是由模板标签来完成的。
对于所有不使用 HTTP GET、HEAD、OPTIONS 或 TRACE 的传入请求,必须存在一个 CSRF cookie,并且“csrfmiddlewaretoken”字段必须存在且正确。如果不存在,用户将得到一个403 错误。
当验证“csrfmiddlewaretoken”字段值时,只有密钥,而不是完整的令牌,会与 cookie 值中的密钥进行比较。这允许使用不断变化的令牌。虽然每个请求都可能使用自己的令牌,但密钥对所有请求都是通用的。
这个检查是由 CsrfViewMiddleware
完成的。
此外,对于 HTTPS 请求,CsrfViewMiddleware
会进行严格的 referer 检查。这意味着即使一个子域可以在你的域上设置或修改 cookie,它也不能强迫用户向你的应用程序提交,因为该请求不会来自你自己的确切域。
这也解决了在 HTTPS 下使用独立于会话的密钥时可能出现的中间人攻击问题,这是因为 HTTP Set-Cookie
头会被客户接受(不幸的是),即使他们在 HTTPS 下与一个网站对话。对 HTTP 请求不进行 Referer 检查,因为 HTTP 下 Referer
头的存在不够可靠)。
如果设置了 CSRF_COOKIE_DOMAIN
设置,则会将 referer 与之进行比较。你可以通过包含一个前导点号来允许跨子域请求。例如,CSRF_COOKIE_DOMAIN = '.example.com'
将允许来自 www.example.com
和 api.example.com
的 POST 请求。如果没有设置,那么 referer 必须与 HTTP Host
头匹配。
通过CSRF_TRUSTED_ORIGINS
设置,可以将接受的 referer 扩展到当前主机或 cookie 域之外。
这确保了只有源自受信任域的表单才能用于 POST 回数据。
它故意忽略了 GET 请求。RFC 7231#section-4.2.1将 POST、PUT 和 DELETE 定义为“不安全”,所有其他方法也被认为是不安全的,以获得最大的保护。
CSRF 保护不能防止中间人攻击,所以使用HTTPS与HTTP 严格传输安全。它还假设验证 HOST头和你的网站上没有任何跨站脚本漏洞(因为 XSS 漏洞已经让攻击者做了 CSRF 漏洞允许的任何事情,甚至更糟)。