【Django网络安全】如何正确防护CSRF跨站点请求伪造
Django网络安全
【Django网络安全】如何正确防护CSRF跨站点请求伪造
文章目录
- Django网络安全
- 前言
- 一、CSRF攻击场景
- 二、CSRF攻击的防御手段
-
- 1.验证 HTTP Referer 字段
- 2.请求地址添加token并验证
- 三、Django的CSRF防御解析
-
- 1.CSRF防护的过程
- 2.cookie中的csrftoken
- 3.session中的csrftoken
- 4.html中的csrftoken
- 5.装饰器中的csrf函数
- 四、前后端不分离场景的正确防御
-
- 1.django模板中form表单提交
- 2.django模板中ajax提交
- 五、前后端分离场景的正确防御
-
- 1.django提供接口
- 2.现在所有函数都应该去除csrf装饰器
- 3.vue获取csrftoken添加至请求
- 六、错误:请求中无cookie参数
- 总结
前言
CSRF(Cross-site request forgery),中文名跨站点请求伪造。当恶意网站包含一个链接、一个表单按钮或一些javascript,使用登录用户在浏览器中的凭据,打算恶意访问您的网站并执行某些操作时,就会发生这种攻击。还包括一种相关的攻击类型“登录CSRF”,即攻击站点诱使用户的浏览器使用他人的凭据登录站点。
XSS和CSRF正好相反,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。直接的说就是我们需要保护POST、PUT和DELETE请求。
向网友借个图
一、CSRF攻击场景
对于用户的的所有操作,除了get查询,其他请求都需要保护,包括POST、PUT和DELETE。
发送邮件
修改账户信息
资金转账
盗取用户隐私数据
网站被上传网马
作为其他攻击方式的辅助攻击(比如xss)
传播CSRF蠕虫(见下文中的YouTube CSRF漏洞)
等等
二、CSRF攻击的防御手段
1.验证 HTTP Referer 字段
这种方式理论上可行,实际情况是Referer可以被伪造。
2.请求地址添加token并验证
django的验证方式是csrftoken验证。实现的主要是长cookie中csrftoken和表单中的csrfmiddlewaretoken是否为同一加密编码来源来实现csrf防御。csrftoken和csrfmiddlewaretoken这两个参数在接下来是我们关注的重点。
三、Django的CSRF防御解析
django的csrf防御是通过中间价来执行,默认是开启状态。位置是django项目的settings文件。
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','utils.middleware.LogMiddleware.OpLog', # 访问记录
]
1.CSRF防护的过程
CsrfViewMiddleware中间件使用的情况下,通过中间件的process_request方法获取请求cookie中的csrftoken,通过process_view获取post、put、delete请求cookie中的csrftoken和表单中的csrfmiddlewaretoken并进行校验(不通过就是403),通过process_response设置cookie的csrftoken参数(需要登录)。
2.cookie中的csrftoken
这里我都是用xadmin后端来验证的,也可以使用admin后端。首先看下get请求中的cookie中csrftoken参数。
我们再来看下post请求cookie中的csrftoken参数和表单中的csrfmiddlewaretoken参数。
3.session中的csrftoken
在第1部分中我们看到请求的cookie中是存在csrftoken参数的,而且这个参数时长是365天的长cookie。csrftoken其实是每次用户登录时生成的csrftoken,如果用户不注销或登出(django的logout方法),这个参数不会改变,并且每次重新登录时刷新csrftoken。
我们知道cookie时保留在客户端浏览器上的,浏览器是无法长久保留cookie的,可能因为客户端种种问题而丢失(浏览器崩溃、重装、更换浏览器等)。这里就衍生出了将csrftoken保存至sessions,通过服务器来保存csrftoken,并通过后端来验证csrftoken。
具体操作是需要在django的settings中修改配置,使cookie保存至sessions。
CSRF_USE_SESSIONS=True # 在用户会话中而不是在cookie中存储CSRF令牌,实际意义不大。
4.html中的csrftoken
在第1部分中我们看到了表单中的csrfmiddlewaretoken参数,在django的使用中,我们会在表单中使用csrftoken
<form method="post">{% csrf_token %} </form>
表单中的csrf_token 实际上就是csrfmiddlewaretoken。
5.装饰器中的csrf函数
我们经常看到很多教程在遇到403报错时,会在django后端使用csrf_exempt装饰器。一般都是在def方法中使用@csrf_exempt,在class类方法中使用@method_decorator(csrf_exempt, name=‘dispatch’)。
csrf_exempt的真正原理是改变csrf_exempt状态为True,在CsrfViewMiddleware中间件的process_view方法中直接跳过csrftoken和csrfmiddlewaretoken的加密校验。
四、前后端不分离场景的正确防御
在django前后端不分离项目中,django通过render方法实现了在表单中生成CsrfViewMiddleware参数。
1.django模板中form表单提交
submit提交
<form method="post">{% csrf_token %} </form>
2.django模板中ajax提交
前端共有三种方式通过验证,示例如下:
方式一:
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');
方式二:
# 也可以通过JavaScript Cookie获得。
const csrftoken = Cookies.get('csrftoken');
方式三:
{% csrf_token %} # 在HTML中包含CSRF令牌
<script>const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>
具体的ajax请求处理如下:
<script>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) {// ...});
</script>
综上所述,Cookies中获取的csrftoken写入到表单的csrfmiddlewaretoken肯定时能校验通过的。前边我们说到,cookie中可以存储csrftoken,sessions中也可以,针对不同的配置,使用的方式时不同的。主要是CSRF_USE_SESSIONS 和 CSRF_COOKIE_HTTPONLY参数。
当CSRF_USE_SESSIONS或CSRF_COOKIE_HTTPONLY 为默认值,即Flase时,三种方式均可。
当CSRF_USE_SESSIONS或CSRF_COOKIE_HTTPONLY 为True时,只能使用方式三。
五、前后端分离场景的正确防御
我使用的前后端分离是基于django+vue的,django后端使用drf和jwt,并且通过django-cors-headers处理跨域问题。在涉及到网络安全的时候,处理csrf防御问题衍生出了本博客。基于cookie和jwt机制,jwt是不需要处理csrf攻击的,csrf针对的是cookie验证登录方式。
JWT机制如果需要实现CSRF防护,需要后端提供csrfmiddlewaretoken参数供前端调用,并在使用post、put和delete请求时添加到headers中。
我们项目需要处理请求的幂等性,将csrfmiddlewaretoken设置为幂等性的验证token。
1.django提供接口
urls.py文件
urlpatterns = [path('get_csrf_token/', get_csrf_token), # 获取csrf_token
]
views.py文件
from django.middleware.csrf import get_tokendef get_csrf_token(request):csrftoken= get_token(request)return JsonResponse({'csrftoken': csrftoken})
2.现在所有函数都应该去除csrf装饰器
3.vue获取csrftoken添加至请求
六、错误:请求中无cookie参数
django系统需要实现登录,response中才会附加cookie和sessions信息,示例如下:
def post(self, request):data = request.dataif 'username' in data and 'password' in data:username = data['username']password = data['password']user = authenticate(request, username=username, password=password)if user:login(request, user) # 这一步很重要token = create_token({'username': username, "id": user.id})return Response({'code': 0,'msg': 'ok','token': token,})return Response({'code': -10,'msg': '用户名或密码错误'})
总结
处理csrf的过程中,使用csrf装饰器的都是干扰项,整理出这篇文章真的很不容易,希望所有小伙伴能够愉快处理csef,如有问题欢迎指正。