天天看點

Django10—安全CSRF攻擊XSS攻擊SQL注入

文章目錄

  • CSRF攻擊
    • 概念
    • 原理
    • 防禦
    • 使用ajax處理csrf防禦
  • XSS攻擊
    • 概念
    • 場景
    • 防禦
    • bleach庫
  • SQL注入
    • 概念
    • 場景
    • 防禦
    • Django中sql注入的防禦

CSRF攻擊

概念

CSRF

(Cross Site Request Forgery,

跨站域請求僞造

)是一種網絡的攻擊方式,它在 2007 年曾被列為網際網路 20 大安全隐患之一。其他安全隐患,比如 SQL 腳本注入,跨站域腳本攻擊等在近年來已經逐漸為衆人熟知,很多網站也都針對他們進行了防禦。然而,對于大多數人來說,CSRF 卻依然是一個陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在着 CSRF 漏洞,進而被黑客攻擊而使 Gmail 的使用者造成巨大的損失。

原理

網站是通過

cookie

來實作登入功能的。而

cookie

隻要存在浏覽器中,那麼浏覽器在通路這個cookie的伺服器的時候,就會自動的攜帶cookie資訊到伺服器上去。那麼這時候就存在一個漏洞了,如果你通路了一個别有用心或病毒網站,這個網站可以在網頁源代碼中插入js代碼,使用js代碼給其他伺服器發送請求(比如ICBC的轉賬請求)。那麼因為在發送請求的時候,浏覽器會自動的把cookie發送給對應的伺服器,這時候相應的伺服器(比如ICBC網站),就不知道這個請求是僞造的,就被欺騙過去了。進而達到在使用者不知情的情況下,給某個伺服器發送了一個請求(比如轉賬)。

防禦

CSRF攻擊的要點就是在向伺服器發送請求的時候,相應的cookie會自動的發送給對應的伺服器。造成伺服器不知道這個請求是使用者發起的還是僞造的。這時候,我們可以在使用者每次通路有表單的頁面的時候,在網頁源代碼中加一個随機的字元串叫做

csrf_token

,在cookie中也加入一個相同值的csrf_token字元串。以後給伺服器發送請求的時候,必須在body中以及cookie中都攜帶csrf_token,伺服器隻有檢測到cookie中的csrf_token和body中的csrf_token都相同,才認為這個請求是正常的,否則就是僞造的。那麼黑客就沒辦法僞造請求了。在Django中,如果想要防禦CSRF攻擊,應該做兩步工作。第一個是在

settings.MIDDLEWARE

中添加

CsrfMiddleware

中間件。第二個是在模版代碼中添加一個input标簽,加載

csrf_token

。示例代碼如下:

  • 伺服器代碼
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'
]
           
  • 模闆代碼

或者是直接使用csrf_token标簽,來自動生成一個帶有csrf token的input标簽:

使用ajax處理csrf防禦

如果用

ajax

來處理

csrf

防禦,那麼需要手動的在form中添加

csrfmiddlewaretoken

,或者是在請求頭中添加

X-CSRFToken

。我們可以從傳回的

cookie

中提取

csrf token

,再設定進去。示例代碼如下:

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 = 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;
}

var myajax = {
    'get': function (args) {
        args['method'] = 'get';
        this.ajax(args);
    },
    'post': function (args) {
        args['method'] = 'post';
        this._ajaxSetup();
        this.ajax(args);
    },
    'ajax': function (args) {
        $.ajax(args);
    },
    '_ajaxSetup': function () {
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
                }
            }
        });
    }
};

$(function () {
    $("#submit").click(function (event) {
        event.preventDefault();
        var email = $("input[name='email']").val();
        var money = $("input[name='money']").val();

        myajax.post({
            'url': '/transfer/',
            'data':{
                'email': email,
                'money': money
            },
            'success': function (data) {
                console.log(data);
            },
            'fail': function (error) {
                console.log(error);
            }
        });
    })
});
           
iframe相關知識:
  • iframe可以加載嵌入别的域名下的網頁。也就是說可以發送跨域請求。比如我可以在我自己的網頁中加載百度的網站,示例代碼如下:
  • 因為iframe加載的是别的域名下的網頁。根據同源政策,js隻能操作屬于本域名下的代碼,是以js不能操作通過iframe加載來的DOM元素。
  • 如果ifrmae的src屬性為空,那麼就沒有同源政策的限制,這時候我們就可以操作iframe下面的代碼了。并且,如果src為空,那麼我們可以在iframe中,給任何域名都可以發送請求。
  • 直接在iframe中寫html代碼,浏覽器是不會加載的。

XSS攻擊

概念

XSS

(Cross Site Script)攻擊又叫做

跨站腳本攻擊

。他的原理是使用者在使用具有XSS漏洞的網站的時候,向這個網站送出一些惡意的代碼,當使用者在通路這個網站的某個頁面的時候,這個惡意的代碼就會被執行,進而來破壞網頁的結構,擷取使用者的隐私資訊等。

場景

比如

A網站

有一個釋出文章的入口,如果使用者在送出資料的時候,送出一段

js

代碼:

<script>alert("hello world");</script>

,然後A網站在渲染這個文章的時候,直接把這個代碼渲染了,那麼這個代碼就會執行,會在浏覽器的視窗中彈出一個模态對話框來顯示hello world!如果攻擊者能成功的運作以上這麼一段js代碼,那他能做的事情就有很多很多了!

防禦

  • 如果不需要顯示一些富文本,那麼在渲染使用者送出的資料的時候,直接進行轉義就可以了。在Django的模闆中預設就是轉義的。也可以把資料在存儲到資料庫之前,就轉義再存儲進去,這樣以後在渲染的時候,即使不轉義也不會有安全問題,示例代碼如下:
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')
           
  • 如果對于使用者送出上來的資料包含了一些富文本(比如:給字型換色,字型加粗等),那麼這時候我們在渲染的時候也要以富文本的形式進行渲染,也即需要使用safe過濾器将其标記為安全的,這樣才能顯示出富文本樣式。但是這樣又會存在一個問題,如果使用者送出上來的資料存在攻擊的代碼呢,那将其标記為安全的肯定是有問題的。示例代碼如下:
# views.py
def index(request):
    message = "<span style='color:red;'>紅色字型</span><script>alert('hello world');</script>";
    return render_template(request,'index.html',context={"message":message})
           

那麼這時候該怎麼辦呢?這時候我們可以指定某些标簽我們是需要的(比如:span标簽),而某些标簽我們是不需要的(比如:script)那麼我們在伺服器處理資料的時候,就可以将這些需要的标簽保留下來,把那些不需要的标簽進行轉義,或者幹脆移除掉,這樣就可以解決我們的問題了。這個方法是可行的,包括很多線上網站也是這樣做的,在Python中,有一個庫可以專門用來處理這個事情,那就是sanitizer。接下來講下這個庫的使用。

bleach庫

bleach庫是用來清理包含html格式字元串的庫。他可以指定哪些标簽需要保留,哪些标簽是需要過濾掉的。也可以指定标簽上哪些屬性是可以保留,哪些屬性是不需要的。想要使用這個庫,可以通過以下指令進行安裝:

參考資料

github位址: https://github.com/mozilla/bleach

文檔位址: https://bleach.readthedocs.io/

pip install bleach
           

這個庫最重要的一個方法是bleach.clean方法,bleach.clean示例代碼如下:

import bleach
from bleach.sanitizer import ALLOWED_TAGS,ALLOWED_ATTRIBUTES

@require_http_methods(['POST'])
def message(request):
    # 從用戶端中擷取送出的資料
    content = request.POST.get('content')

    # 在預設的允許标簽中添加img标簽
    tags = ALLOWED_TAGS + ['img']
    # 在預設的允許屬性中添加src屬性
    attributes = {**ALLOWED_ATTRIBUTES,'img':['src']}

    # 對送出的資料進行過濾
    cleaned_content=bleach.clean(content,tags=tags,attributes=attributes)

    # 儲存到資料庫中
    Message.objects.create(content=cleaned_content)

    return redirect(reverse('index'))
           

相關介紹如下:

  • tags:表示允許哪些标簽。
  • attributes:表示标簽中允許哪些屬性。
  • ALLOWED_TAGS:這個變量是bleach預設定義的一些标簽。如果不符合要求,可以對其進行增加或者删除。
  • ALLOWED_ATTRIBUTES:這個變量是bleach預設定義的一些屬性。如果不符合要求,可以對其進行增加或者删除。

SQL注入

概念

所謂SQL注入,就是通過把SQL指令插入到表單中或頁面請求的查詢字元串中,最終達到欺騙伺服器執行惡意的SQL指令。具體來說,它是利用現有應用程式,将(惡意的)SQL指令注入到背景資料庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的資料庫,而不是按照設計者意圖去執行SQL語句。 比如先前的很多影視網站洩露VIP會員密碼大多就是通過WEB表單遞交查詢字元暴出的。

場景

比如現在資料庫中有一個front_user表,表結構如下:

class User(models.Model):
    telephone = models.CharField(max_length=11)
    username = models.CharField(max_length=100)
    password = models.CharField(max_length=100)
           
  • 實作一個根據使用者id擷取使用者詳情的視圖。示例代碼如下:
    def index(request):
             user_id = request.GET.get('user_id')
             cursor = connection.cursor()
             cursor.execute("select id,username from front_user where id=%s" % user_id)
             rows = cursor.fetchall()
             for row in rows:
                 print(row)
             return HttpResponse('success')
               
    這樣表面上看起來沒有問題。但是如果使用者傳的user_id是等于1 or 1=1,那麼以上拼接後的sql語句為:
    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表中所有的資料都提取出來。
  • 實作一個根據使用者的username提取使用者的視圖。示例代碼如下:
    def index(request):
         username = request.GET.get('username')
         cursor = connection.cursor()
         cursor.execute("select id,username from front_user where username='%s'" % username)
         rows = cursor.fetchall()
         for row in rows:
             print(row)
         return HttpResponse('success')
               
    這樣表面上看起來也沒有問題。但是如果使用者傳的username是zhiliao’ or '1=1,那麼以上拼接後的sql語句為:

防禦

以上便是sql注入的原理。他通過傳遞一些惡意的參數來破壞原有的sql語句以便達到自己的目的。當然sql注入遠遠沒有這麼簡單,我們現在講到的隻是冰山一角。那麼如何防禦sql注入呢?歸類起來主要有以下幾點:

  1. 永遠不要信任使用者的輸入。對使用者的輸入進行校驗,可以通過正規表達式,或限制長度;對單引号和 雙"-"進行轉換等。
  2. 永遠不要使用動态拼裝sql,可以使用參數化的sql或者直接使用存儲過程進行資料查詢存取。比如:
    def index(request):
     user_id = "1 or 1=1"
     cursor = connection.cursor()
     cursor.execute("select id,username from front_user where id=%s",(user_id,))
     rows = cursor.fetchall()
     for row in rows:
         print(row)
     return HttpResponse('success')
               
  3. 永遠不要使用管理者權限的資料庫連接配接,為每個應用使用單獨的權限有限的資料庫連接配接。
  4. 不要把機密資訊直接存放,加密或者hash掉密碼和敏感的資訊。
  5. 應用的異常資訊應該給出盡可能少的提示,最好使用自定義的錯誤資訊對原始錯誤資訊進行包裝。

Django中sql注入的防禦

在Django中如何防禦sql注入:

  • 使用ORM來做資料的增删改查。因為ORM使用的是參數化的形式執行sql語句的。
  • 如果萬一要執行原生sql語句,那麼建議不要拼接sql,而是使用參數化的形式。

#錯誤–不要直接格式化字元串

query = ‘SELECT * FROM myapp_person WHERE last_name = %s’ % lname

Person.objects.raw(query)

#正确–使用Django raw函數 功能進行安全轉義

name = ‘Doe’

Person.objects.raw(‘SELECT * FROM myapp_person WHERE last_name = %s’, [name])

繼續閱讀