CSRF攻击:

CSRF攻击概述:

CSRF(Cross Site Request Forgery, 跨站请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一。其他安全隐患,比如 SQL 脚本注入,跨站域脚本攻击等在近年来已经逐渐为众人熟知,很多网站也都针对他们进行了防御。然而,对于大多数人来说,CSRF 却依然是一个陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在着 CSRF 漏洞,从而被黑客攻击而使 Gmail 的用户造成巨大的损失。

CSRF攻击原理:

网站是通过cookie来实现登录功能的。而cookie只要存在浏览器中,那么浏览器在访问这个cookie的服务器的时候,就会自动的携带cookie信息到服务器上去。那么这时候就存在一个漏洞了,如果你访问了一个别有用心或病毒网站,这个网站可以在网页源代码中插入js代码,使用js代码给其他服务器发送请求(比如ICBC的转账请求)。那么因为在发送请求的时候,浏览器会自动的把cookie发送给对应的服务器,这时候相应的服务器(比如ICBC网站),就不知道这个请求是伪造的,就被欺骗过去了。从而达到在用户不知情的情况下,给某个服务器发送了一个请求(比如转账)。

防御CSRF攻击:

CSRF攻击的要点就是在向服务器发送请求的时候,相应的cookie会自动的发送给对应的服务器。造成服务器不知道这个请求是用户发起的还是伪造的。这时候,我们可以在用户每次访问有表单的页面的时候,在网页源代码中加一个随机的字符串叫做csrf_token,在cookie中也加入一个相同值的csrf_token字符串。以后给服务器发送请求的时候,必须在body中以及cookie中都携带csrf_token,服务器只有检测到cookie中的csrf_tokenbody中的csrf_token都相同,才认为这个请求是正常的,否则就是伪造的。那么黑客就没办法伪造请求了。在Django中,如果想要防御CSRF攻击,应该做两步工作。第一个是在settings.MIDDLEWARE中添加CsrfMiddleware中间件。第二个是在模版代码中添加一个input标签,加载csrf_token。示例代码如下:

  • 服务器代码: ```python MIDDLEWARE = [ ‘django.middleware.security.SecurityMiddleware’, ‘django.middleware.gzip.GZipMiddleware’, ‘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’ ]
  1. - 模版代码:
  2. ```html
  3. <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}"/>


或者是在表单中直接使用csrf_token标签{% csrf_token %},来自动生成一个带有csrf tokeninput标签:

  1. <form action="" method="post">
  2. {% csrf_token %}
  3. {# 此处编写表单代码 #}
  4. </form>

使用ajax处理csrf防御:

如果用ajax来处理csrf防御,那么需要手动的在表单中添加csrfmiddlewaretoken,或者是在请求头中添加X-CSRFToken。我们可以从返回的cookie中提取csrf token,再设置进去。示例代码如下:

  1. // 这个方法是用来获取cookie的
  2. function getCookie(name) {
  3. var cookieValue = null;
  4. if (document.cookie && document.cookie !== '') {
  5. var cookies = document.cookie.split(';');
  6. for (var i = 0; i < cookies.length; i++) {
  7. var cookie = jQuery.trim(cookies[i]);
  8. // Does this cookie string begin with the name we want?
  9. if (cookie.substring(0, name.length + 1) === (name + '=')) {
  10. cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
  11. break;
  12. }
  13. }
  14. }
  15. return cookieValue;
  16. }
  17. var myajax = {
  18. 'get': function (args) {
  19. args['method'] = 'get';
  20. this.ajax(args);
  21. },
  22. 'post': function (args) {
  23. args['method'] = 'post';
  24. this._ajaxSetup();
  25. this.ajax(args);
  26. },
  27. 'ajax': function (args) {
  28. $.ajax(args);
  29. },
  30. '_ajaxSetup': function () {
  31. $.ajaxSetup({
  32. beforeSend: function(xhr, settings) {
  33. if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type) && !this.crossDomain) {
  34. xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
  35. }
  36. }
  37. });
  38. }
  39. };
  40. $(function () {
  41. $("#submit").click(function (event) {
  42. event.preventDefault();
  43. var email = $("input[name='email']").val();
  44. var money = $("input[name='money']").val();
  45. myajax.post({
  46. 'url': '/transfer/',
  47. 'data':{
  48. 'email': email,
  49. 'money': money
  50. },
  51. 'success': function (data) {
  52. console.log(data);
  53. },
  54. 'fail': function (error) {
  55. console.log(error);
  56. }
  57. });
  58. })
  59. });

iframe相关知识:

  1. iframe可以加载嵌入别的域名下的网页。也就是说可以发送跨域请求。比如我可以在我自己的网页中加载百度的网站,示例代码如下:

    1. <iframe src="http://www.baidu.com/">
    2. </iframe>
  2. 因为iframe加载的是别的域名下的网页。根据同源策略js只能操作属于本域名下的代码,因此js不能操作通过iframe加载来的DOM元素。

  3. 如果ifrmaesrc属性为空,那么就没有同源策略的限制,这时候我们就可以操作iframe下面的代码了。并且,如果src为空,那么我们可以在iframe中,给任何域名都可以发送请求。
  4. 直接在iframe中写html代码,浏览器是不会加载的,因此可以将写好的form表单放在iframe外面,运行时使用JS再将form添加到iframe中,此时,就可以——为所欲为(实现一个完美的CSRF攻击)了。

XSS攻击

XSS(Cross Site Script)攻击又叫做跨站脚本攻击。它的原理是用户在使用具有XSS漏洞的网站的时候,向这个网站提交一些恶意的代码,当用户在访问这个网站的某个页面的时候,这个恶意的代码就会被执行,从而来破坏网页的结构,获取用户的隐私信息等。

XSS攻击场景:

比如A网站有一个发布帖子的入口,如果用户在提交数据的时候,提交了一段js代码比如:<script>alert("hello world");</script>,然后A网站在渲染这个帖子的时候,直接把这个代码渲染了,那么这个代码就会执行,会在浏览器的窗口中弹出一个模态对话框来显示hello world!如果攻击者能成功的运行以上这么一段js代码,那他能做的事情就有很多很多了!

XSS攻击防御:

  1. 如果不需要显示一些富文本,那么在渲染用户提交的数据的时候,直接转义就可以了。在Django的模板中默认就是转义的。也可以把数据在存储到数据库之前,就转义再存储进去,这样以后在渲染的时候,即使不转义也不会有安全问题,示例代码如下: ```python from django.template.defaultfilters import escape from .models import Comment from django.http import HttpResponse

def comment(request): content = request.POST.get(“content”) escaped_content = escape(content) Comment.objects.create(content=escaped_content) return HttpResponse(‘success’)

  1. 2. 如果对于用户提交上来的数据包含了一些**富文本**(比如:给字体换色,字体加粗等),那么这时候我们在渲染的时候也要以富文本的形式进行渲染,也即需要使用`safe`过滤器将其标记为安全的,这样才能显示出富文本样式。但是这样又会存在一个问题,如果用户提交上来的数据存在攻击的代码呢,那将其标记为安全的肯定是有问题的。示例代码如下:
  2. ```python
  3. # views.py
  4. def index(request):
  5. message = "<span style='color:red;'>红色字体</span><script>alert('hello world');</script>";
  6. return render_template(request, 'index.html', context={"message": message})


那么这时候该怎么办呢?这时候我们可以指定某些标签我们是需要的(比如:span标签),而某些标签我们是不需要的(比如:script)。那么我们在服务器处理数据的时候,就可以将需要的标签保留下来,把那些不需要的标签进行转义,或者干脆移除掉,这样就可以解决我们的问题了。这个方法是可行的,包括很多线上网站也是这样做的,在Python中,有一个库可以专门用来处理这个事情,那就是bleach。接下来讲下这个库的使用。

bleach库:

bleach库是用来清理包含html格式字符串的库。他可以指定哪些标签需要保留,哪些标签是需要过滤掉的。也可以指定标签上哪些属性是可以保留,哪些属性是不需要的。想要使用这个库,可以通过以下命令进行安装:

  1. pip install bleach

这个库最重要的一个方法是bleach.clean方法,bleach.clean示例代码如下:

  1. import bleach
  2. from bleach.sanitizer import ALLOWED_TAGS,ALLOWED_ATTRIBUTES
  3. @require_http_methods(['POST'])
  4. def message(request):
  5. # 从客户端中获取提交的数据
  6. content = request.POST.get('content')
  7. # 在默认的允许标签中添加img标签
  8. tags = ALLOWED_TAGS + ['img']
  9. # 在默认的允许属性中添加src属性
  10. attributes = {**ALLOWED_ATTRIBUTES,'img':['src']}
  11. # 对提交的数据进行过滤
  12. cleaned_content = bleach.clean(content,tags=tags,attributes=attributes)
  13. # 保存到数据库中
  14. Message.objects.create(content=cleaned_content)
  15. return redirect(reverse('index'))

相关介绍如下:

  1. tags:表示允许哪些标签。
  2. attributes:表示标签中允许哪些属性。
  3. ALLOWED_TAGS:这个变量是bleach默认定义的一些标签。如果不符合要求,可以对其进行增加或者删除。
  4. ALLOWED_ATTRIBUTES:这个变量是bleach默认定义的一些属性。如果不符合要求,可以对其进行增加或者删除。

bleach更多资料:

  1. github地址: https://github.com/mozilla/bleach
  2. 文档地址: https://bleach.readthedocs.io/

clickjacking攻击:

clickjacking攻击又称作点击劫持攻击。是一种在网页中将恶意代码等隐藏在看似无害的内容(如按钮)之下,并诱使用户点击的手段。

clickjacking攻击场景:

场景一:

如用户收到一封包含一段视频的电子邮件,但其中的“播放”按钮并不会真正播放视频,而是链入一购物网站。这样当用户试图“播放视频”时,实际是被诱骗而进入了一个购物网站。

场景二:

用户进入到一个网页中,里面包含了一个非常有诱惑力的按钮A,但是这个按钮上面浮了一个透明的iframe标签,这个iframe标签加载了另外一个网页,并且他将这个网页的某个按钮和原网页中的按钮A重合,所以你在点击按钮A的时候,实际上点的是通过iframe加载的另外一个网页的按钮。比如我现在有一个百度贴吧,想要让更多的用户来关注,那么我们可以准备以下一个页面:

  1. <!DOCTYPE html>
  2. <html>
  3. <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
  4. <head>
  5. <title>点击劫持</title>
  6. <style>
  7. iframe{
  8. opacity:0.01;
  9. position:absolute;
  10. z-index:2;
  11. width: 100%;
  12. height: 100%;
  13. }
  14. button{
  15. position:absolute;
  16. top: 345px;
  17. left: 630px;
  18. z-index: 1;
  19. width: 72px;
  20. height: 26px;
  21. }
  22. </style>
  23. </head>
  24. <body>
  25. 这个合影里面怎么会有你?
  26. <button>查看详情</button>
  27. <iframe src="http://tieba.baidu.com/f?kw=%C3%C0%C5%AE"></iframe>
  28. </body>
  29. </html>
  30. 页面看起来比较简陋,但是实际上可能会比这些更精致一些。当这个页面通过某种手段被传播出去后,用户如果点击了“查看详情”,实际上点击到的是关注的按钮,这样就可以增加了一个粉丝。

clickjacking防御:

像以上场景1,是没有办法避免的,受伤害的是用户。而像场景2,受伤害的是百度贴吧网站和用户。这种场景是可以避免的,只要设置百度贴吧不允许使用iframe被加载到其他网页中,就可以避免这种行为了。我们可以通过在响应头中设置X-Frame-Options来设置这种操作。X-Frame-Options可以设置以下三个值:

  1. DENY:不让任何网页使用iframe加载我这个页面。
  2. SAMEORIGIN:只允许在相同域名(也就是我自己的网站)下使用iframe加载我这个页面。
  3. ALLOW-FROM origin:允许任何网页通过iframe加载我这个网页。

Django中,使用中间件django.middleware.clickjacking.XFrameOptionsMiddleware可以帮我们堵上这个漏洞,这个中间件设置了X-Frame-OptionSAMEORIGIN,也就是只有在自己的网站下才可以使用iframe加载这个网页,这样就可以避免其他别有心机的网页去通过iframe去加载了。

SQL注入

所谓SQL注入,就是通过把SQL命令插入到表单中或页面请求的查询字符串中,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。 比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的。

场景:

比如现在数据库中有一个front_user表,表结构如下:

  1. class User(models.Model):
  2. telephone = models.CharField(max_length=11)
  3. username = models.CharField(max_length=100)
  4. password = models.CharField(max_length=100)

然后我们使用原生sql语句实现以下需求:

  1. 实现一个根据用户id获取用户详情的视图。示例代码如下:

    1. def index(request):
    2. user_id = request.GET.get('user_id')
    3. cursor = connection.cursor()
    4. cursor.execute("select id,username from front_user where id=%s" % user_id)
    5. rows = cursor.fetchall()
    6. for row in rows:
    7. print(row)
    8. return HttpResponse('success')


    这样表面上看起来没有问题。但是如果用户传的user_id是等于1 or 1=1,那么以上拼接后的sql语句为:

    1. select id,username from front_user where id=1 or 1=1


    以上sql语句的条件是id=1 or 1=1,只要id=1或者是1=1两个有一个成立,那么整个条件就成立。毫无疑问1=1是肯定成立的。因此执行完以上sql语句后,会将front_user表中所有的数据都提取出来。 是不是很恐怖?

  2. 实现一个根据用户的username提取用户的视图。示例代码如下:

    1. def index(request):
    2. username = request.GET.get('username')
    3. cursor = connection.cursor()
    4. cursor.execute("select id,username from front_user where username='%s'" % username)
    5. rows = cursor.fetchall()
    6. for row in rows:
    7. print(row)
    8. return HttpResponse('success')


    这样表面上看起来也没有问题。但是如果用户传的usernamezhiliao' or '1=1,那么以上拼接后的sql语句为:

    1. select id,username from front_user where username='zhiliao' or '1=1'

    以上sql语句的条件是username='zhiliao'或者是一个字符串,毫无疑问,字符串的判断是肯定成立的。因此会将front_user表中所有的数据都提取出来。

上面的两个小栗子是不是挺吓人?其实这只是SQL注入的冰山一角,高手黑客可以玩出更多花样,如果你的网站没有对该类型的攻击做出防范,那你的数据库几乎就是在裸奔!人家想怎么搞就怎么搞!

sql注入防御:

以上便是sql注入的原理。他通过传递一些恶意的参数来破坏原有的sql语句以便达到自己的目的。当然sql注入远远没有这么简单,我们现在讲到的只是冰山一角。那么如何防御sql注入呢?归类起来主要有以下几点:

  1. 永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和 双”-“进行转换等。
  2. 永远不要使用动态拼装**sql**,可以使用参数化sql或者直接使用存储过程进行数据查询存取。比如:
    1. def index(request):
    2. user_id = "1 or 1=1"
    3. cursor = connection.cursor()
    4. cursor.execute("select id,username from front_user where id=%s", (user_id,))
    5. rows = cursor.fetchall()
    6. for row in rows:
    7. print(row)
    8. return HttpResponse('success')
  1. 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
  2. 永远不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
  3. 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装。

在Django中如何防御sql注入:

  1. 使用ORM来做数据的增删改查。因为ORM使用的是参数化的形式执行sql语句的。
  2. 如果万一要执行原生sql语句,那么建议不要拼接sql,而是使用参数化的形式。