1. 程式人生 > >Django中CSRF原理及應用詳解

Django中CSRF原理及應用詳解

 Web開發中十分重要的一項內容就是Web安全,在我們進行服務端構建的時候,最常見的幾種Web攻擊無非是下面的這幾種:

           1.注入(SQL注入)

           2.跨站指令碼攻擊(XSS)

           3.跨站請求偽造(CSRF)

           4.開放重定向

今天主要就CSRF做一個簡單的總結,結合Python Web框架Django 做一個應用

一、CSRF是什麼

跨站請求偽造(CSRF)與跨站請求指令碼正好相反。跨站請求指令碼的問題在於,客戶端信任伺服器端傳送的資料。跨站請求偽造的問題在於,伺服器信任來自客戶端的資料。

二、無CSRF時存在的隱患

跨站請求偽造是指攻擊者通過HTTP請求江資料傳送到伺服器,從而盜取回話的cookie。盜取回話cookie之後,攻擊者不僅可以獲取使用者的資訊,還可以修改該cookie關聯的賬戶資訊。

三、Form提交(CSRF)

那麼在Django中CSRF驗證大體是一個什麼樣的原理呢?下面通過一個小例子來簡單說明一下:

我們把Django中CSRF中介軟體開啟(在settings.py中)

'django.middleware.csrf.CsrfViewMiddleware'

在app01的views.py中寫一個簡單的後臺

from django.shortcuts import render,redirect,HttpResponse

# Create your views here.
def login(request):
    if request.method == "GET":
        return render(request,'login.html')
    elif request.method == "POST":
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        if user == 'root' and pwd == "123123":
            #生成隨機字串
            #寫到使用者瀏覽器cookie
            #儲存在服務端session中
            #在隨機字串對應的字典中設定相關內容
            request.session['username'] = user
            request.session['islogin'] = True
            if request.POST.get('rmb',None) == '1':
                #認為設定超時時間
                request.session.set_expiry(10)
            return redirect('/index/')
        else:
            return render(request,'login.html')

def index(request):
    #獲取當前隨機字串
    #根據隨機字串獲取對應的資訊
    if request.session.get('islogin', None):
        return render(request,'index.html',{'username':request.session['username']})
    else:
        return HttpResponse('please login ')

def logout(request):
    del request.session['username']
    request.session.clear()
    return redirect('/login/')

 在templates中寫一個簡單的登陸建立兩個檔案(login.html,index.html)登陸成功跳轉index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登入
        <input type="submit" value="提交" />
    </form>
</body>
</html>

這是瀏覽器中的樣式 :

那麼如果這個時候,我們點選登陸提交,django會因為無法通過csrf驗證返回一個403:

 

       而csrf驗證其實是對http請求中一段隨機字串的驗證,那麼這段隨機字串從何而來呢?這個時候我們嘗試把login.html做一個修改新增一句 {% csrf_token %}:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登入
        <input type="submit" value="提交" />
    </form>
</body>
</html>

這個時候我們再通過瀏覽器元素審查,就會發現一段新的程式碼:

       Django在html中建立一個基於input框value值的隨機字串 ,這個時候我們再次輸入後臺設定的賬號密碼,進行提交,會發現提交成功,進入了我們的index.html介面當中:

      這就是csrf的基本原理,如果沒有這樣一段隨機字串做驗證,我們只要在另一個站點,寫一個表單,提交到這個地址下,是一樣可以傳送資料的,這樣就造成了極大的安全隱患。而我們新新增的csrf_token就是在我們自己的站點中,設定的隱藏引數,用來進行csrf驗證。

四、Ajax提交 (CSRF)

        上面是一個基於form表單提交的小例子,csrf中介軟體要驗證的隨機字串存放在了form表單當中,傳送到了後臺,那麼如果我們使用的是ajax非同步請求,是不是也要傳送這樣一個類似的字串呢?答案是肯定的,但這個字串又該來自哪裡?其實它來自cookie,在上面的那個小例子當中,我們開啟F12元素審查,會發現,在cookie中也存放這樣一個csrftoken:

        所以下面呢我們就再聊一下ajax請求中如何進行。

        首先我們引入兩個js檔案放在工程專案的static當中,這兩個檔案是jquery的庫檔案,方便我們進行請求操作:

        然後我們把之前的login.html做一個修改:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登入
        <input type="submit" value="提交" />
        <input id="btn" type="button" value="按鈕">
    </form>

    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        $(function () {
            $('#btn').click(function () {
                $.ajax({
                    url:'/login/',
                    type:"POST",
                    data:{'username':'root','pwd':'123123'},
                    success:function (arg) {
                        
                    }
                })
            })
        })
    </script>
</body>
</html>

        這個時候我們開啟介面,點選按鈕,會發現http請求傳送403錯誤,很明顯我們直接傳送請求是不合適的,並沒有帶隨機字串過去:

        所以,我們應該先從cookie中獲取到這個隨機字串,這個隨機字串的名字,我們可以通過之前的驗證得出是“csrftoken”:

var csrftoken = $.cookie('csrftoken');

        這樣變數csrftoken取到的就是我們的隨機字串;但是如果後臺想要去接收這個隨機字串,也應該需要一個key,那這個key是什麼?我們可以通過查詢配置檔案,通過控制檯輸出的方式驗證這個key:

from django.conf import settings
print(settings.CSRF_HEADER_NAME)

        最後輸出的是:

HTTP_X_CSRFTOKEN

         但這裡需要注意的是,HTTP_X_CSRFTOKEN並不是請求頭中傳送給django真正拿到的欄位名,前端發過去真正的欄位名是:

X-CSRFtoken

        那為什麼從Django的控制檯輸出會得到HTTP_X_CSRFTOKEN呢?其實我們前端的請求頭X-CSRFtoken傳送到後臺之後,django會做一個名字處理,在原來的欄位名前家一個HTTP_,並且將原來的小寫字元變成大寫的,“-”會處理成下劃線“_”,所以會有這兩個欄位的不一樣。但本質上他們指向的都是同一個字串。知道這一點之後,那麼我們前端就可以真正地發起含有CSRF請求頭的資料請求了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登入
        <input type="submit" value="提交" />
        <input id="btn" type="button" value="按鈕">
    </form>

    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        var csrftoken = $.cookie('csrftoken');
        $(function () {
            $('#btn').click(function () {
                $.ajax({
                    url:'/login/',
                    type:"POST",
                    data:{'username':'root','pwd':'123123'},
                    header:{'X-CSRFtoken':csrftoken},
                    success:function (arg) {
                        
                    }
                })
            })
        })
    </script>
</body>
</html>

       在頁面中點選按鈕之後,會發現請求成功!

       那麼這個時候有人會問,難道所有的ajax請求,都需要這樣獲取一次寫進去嗎,會不會很麻煩。針對這一點,jquery的ajax請求中為我們封裝了一個方法:ajaxSetup,它可以為我們所有的ajax請求做一個集體配置,所以我們可以進行如下改造,這樣不管你的ajax請求有多少,都可以很方便地進行csrf驗證了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="POST">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登入
        <input type="submit" value="提交" />
        <input id="btn" type="button" value="按鈕">
    </form>

    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        var csrftoken = $.cookie('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);
                }
            }
        });

        $(function () {
            $('#btn').click(function () {
                $.ajax({
                    url:'/login/',
                    type:"POST",
                    data:{'username':'root','pwd':'123123'},
                    success:function (arg) {
                        
                    }
                })
            })
        });
    </script>
</body>
</html>

四、裝飾器配置

         在講完基本的csrf驗證操作之後,我們還有一個可說的地方。在平時的產品需求當中,並不一定所有的介面驗證都需要進行csrf驗證,而我們之前採用的是在settings.py中介軟體配置進行全域性配置,那麼如果遇到了不需要開啟csrf的時候該怎麼辦呢?

from django.views.decorators.csrf import csrf_exempt,csrf_protect

@csrf_protect
def index(request):
    .....

@csrf_exempt
def login(request):
    .....

       @csrf_protect 是 開啟csrf驗證的裝飾器,@csrf_exempt是關閉csrf驗證的裝飾器。

五、前後端分離專案手動將csrftoken寫入cookie的方法

1. 手動設定,在view 中新增(經常失效,建議採用2,3,4種方法,親測有效)

request.META["CSRF_COOKIE_USED"] = True

2. 手動呼叫 csrf 中的 get_token(request) 或 rotate_token(request) 方法。

from django.middleware.csrf import get_token ,rotate_token

def server(request):

    # get_token(request)       // 兩者選一
    # rotate_token(request)   // 此方法每次設定新的cookies

    return render(request, ‘server.html‘)

3. 在HTML模板中新增 {% csrf_token %}

4. 在需要設定cookie的檢視上加裝飾器 ensure_csrf_cookie()

from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def server(request):

    return render(request,'server.html')