csrf跨站请求伪造
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。(来源:百度百科)
按下图的例子来说:
tom在浏览器中登陆了银行网站没有退出登陆,此时浏览器中就含有tom在银行认真的cookie信息,然而此时tom不小心浏览了一个钓鱼网站,这个掉钓鱼网站会伪造一个转账的请求,然后发送请求到银行的服务器,此时这个伪造的转账请求带着浏览器中携带的tom登陆银行的认证信息,银行服务器在接收到这个请求后,检查到tom的认证信息,会认定是tom的合法转账操作,最终执行了该操作,tom的账户上金额就会减少,从而钓鱼网站达到了跨站请求伪造。
(图片来源:百度百科)
csrf跨站请求伪造校验
那么该如何规避跨站请求伪造攻击, csrf跨站请求伪造校验就出现了。
什么是 csrf跨站请求伪造校验?
网站在给用户返回一个具有提交数据功能页面的时候回给这个页面添加一个唯一标识,当这个页面向后端发送post请求后,后端会先校验唯一标识,如果标识不对直接拒绝(403 forbidden),如果校验成功就继续执行。
django完成csrf校验
django的csrf跨站请求伪造校验由csrf中间件完成,我们只需要直接配置即可,前期我们使用form表单提交post请求是注释掉该中间件的。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # csrf中间件
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
form表单校验
首先创建一个login页面
def login(request):
return render(request, 'login.html')
<form action="" method="post">
<p>username:<input type="text"></p>
<p>password:<input type="text"></p>
<p><input type="submit" class="btn btn-primary"></p>
</form>
此时提交form表单,会返回403 forbidden,因为我们没有进行csrf验证
在form表单中进行csrf验证只需在form表单中加入{% csrf_token %}即可
<form action="" method="post">
{% csrf_token %}
<p>username:<input type="text"></p>
<p>password:<input type="text"></p>
<p><input type="submit" class="btn btn-primary"></p>
</form>
此时就能向后端发送post请求,并且页面中多出了csrfmiddlewaretoken,这就是后端给浏览器的唯一标识,每次访问都会改变。
ajax进行校验
首先在页面中添加一个ajax请求
<p>
<button class="btn btn-danger" id="btn">点这里发送ajax请求</button>
</p>
<script>
$('#btn').click(function () {
$.ajax({
url: '',
type: 'post',
data: {'user':'wcy', 'pswd':'123'},
success: function () {
alert('发送成功')
}
})
})
</script>
此时访问依然是403forbidden
ajax有三种方法实现验证:
第一种:利用标签获取页面上的csrfmiddlewaretoken,添加到data中
<script>
$('#btn').click(function () {
$.ajax({
url: '',
type: 'post',
data: {'user':'wcy', 'pswd':'123', 'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken]').val()},
success: function () {
alert('发送成功')
}
})
})
</script>
第二种:使用模板语法提供的快捷方式{{ csrf_token }}
<script>
$('#btn').click(function () {
$.ajax({
url: '',
type: 'post',
//第一种
//data: {'user':'wcy', 'pswd':'123', 'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken]').val()},
//第二种
data: {'user':'wcy', 'pswd':'123', 'csrfmiddlewaretoken':'{{ csrf_token }}' },
success: function () {
alert('发送成功')
}
})
})
</script>
第三种:使用js脚本
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var 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;
}
var csrftoken = getCookie('csrftoken');
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", csrftoken);
}
}
});
将上述代码放在静态文件夹static中, 并且配置静态文件路径,导入页面中。
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
{% load static %}
<script src="{% static 'js/mycsrf.js' %}"></script>
<script>
$('#btn').click(function () {
$.ajax({
url: '',
type: 'post',
//第一种
//data: {'user':'wcy', 'pswd':'123', 'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken]').val()},
//第二种
//data: {'user':'wcy', 'pswd':'123', 'csrfmiddlewaretoken':'{% csrf_token %}' },
//第三种 只需要导入js文件即可
data: {'user':'wcy', 'pswd':'123'},
success: function () {
alert('发送成功')
}
})
})
</script>
以上三种方式都能成功完成ajax请求的csrf校验,第三种方式最常用
csrf装饰器
csrf相关的装饰器主要使用以下两个
from django.views.decorators.csrf import csrf_exempt, csrf_protect
"""
csrf_exempt 不需要校验
csrf_protect 需要校验
"""
使用情景:
- 网站整体不需要csrf校验,但一部分需要校验
- 网站整体都需要校验,而一部分不需要
依次来验证上面两点
FBV
网站整体不需要csrf校验,但一部分需要校验,我们在配置文件中注释掉csrf中间件,在login函数上使用csrf_protect装饰器, 此时login需要验证
@csrf_protect
def login(request):
return render(request, 'login.html')
此时如果注释掉form表单中的{% csrf_token %}
<form action="" method="post">
{# {% csrf_token %}#}
<p>username:<input type="text"></p>
<p>password:<input type="text"></p>
<p><input type="submit" class="btn btn-primary"></p>
</form>
将会返回403forbidden错误,添加校验才会继续执行
如果网站整体都需要校验,而一部分不需要,我们打开中间件,在login出使用csrf_exempt装饰器
@csrf_exempt
def login(request):
return render(request, 'login.html')
<form action="" method="post">
{# {% csrf_token %}#}
<p>username:<input type="text"></p>
<p>password:<input type="text"></p>
<p><input type="submit" class="btn btn-primary"></p>
</form>
此时依然能够提交post请求,说明此时login不需要验证
CBV
接下来看CBV添加csrf装饰器,CBV添加装饰器的方式有三种,详情参见CVB添加装饰器
编写CBV
from django.views import View
class MyView(View):
def get(self, request):
return HttpResponse('get方法')
def post(self, request):
return HttpResponse('post方法')
先来看添加csrf_protect
看第一种添加方式
注释掉中间件,在post方法上添加保护,在注释掉form中的
from django.views import View
from django.utils.decorators import method_decorator
class MyView(View):
def get(self, request):
return HttpResponse('get方法')
@method_decorator(csrf_protect)
def post(self, request):
return HttpResponse('post方法')
<form action="/cbv/" method="post">
{# {% csrf_token %}#}
<p>username:<input type="text"></p>
<p>password:<input type="text"></p>
<p><input type="submit" class="btn btn-primary"></p>
</form>
访问login向cbv提交post请求返回403forbidden,说明此时第一种方式生效
第二种方式
from django.views import View
from django.utils.decorators import method_decorator
@method_decorator(csrf_protect, name='post')
class MyView(View):
def get(self, request):
return HttpResponse('get方法')
# @method_decorator(csrf_protect)
def post(self, request):
return HttpResponse('post方法')
访问依然是403页面,说明第二种生效
第三种方式
from django.views import View
from django.utils.decorators import method_decorator
# @method_decorator(csrf_protect, name='post')
class MyView(View):
@method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
return super(MyView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse('get方法')
# @method_decorator(csrf_protect)
def post(self, request):
return HttpResponse('post方法')
访问后还是返回403,说明第三种方法也可以
终上所述,csrf_protect符合CBV添加装饰器的三种方法
然后我们来验证添加csrf_protect
打开csrf中间件,注释掉form表单中的{% csrf_token %}
第一种:
class MyView(View):
def dispatch(self, request, *args, **kwargs):
super(MyView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse('get方法')
@method_decorator(csrf_exempt)
def post(self, request):
return HttpResponse('post方法')
此时访问依然返回403forbidden,说明此时还是需要csrf验证,第一种方法对csrf_exempt不起作用
第二种:
@method_decorator(csrf_exempt, name='post')
class MyView(View):
def dispatch(self, request, *args, **kwargs):
return super(MyView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse('get方法')
def post(self, request):
return HttpResponse('post方法')
此时访问依然返回403forbidden,说明此时还是需要csrf验证,第二种方法对csrf_exempt不起作用
第三种:
class MyView(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(MyView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse('get方法')
def post(self, request):
return HttpResponse('post方法')
此时访问能够成功提交post请求,说明此时不需要csrf验证,第三种方法对csrf_exempt起作用
综上所述,只有第三种方式能添加csrf_ecempt装饰器,第三种方式也可以用第二种方式来表达
@method_decorator(csrf_exempt, name='dispatch')
class MyView(View):
def dispatch(self, request, *args, **kwargs):
return super(MyView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse('get方法')
def post(self, request):
return HttpResponse('post方法')
两种方法等价