1. 程式人生 > >Django CSRF 說明與配置

Django CSRF 說明與配置

文章目錄

一、CSRF是什麼

CSRF 全稱(Cross Site Request Forgery)跨站請求偽造。也被稱為One Click Attack和Session Riding,通常縮寫為CSRF或XSRF。你可以這樣理解:攻擊者(黑客,釣魚網站)盜用了你的身份,以你的 名義傳送惡意請求,這些請求包括髮送郵件、傳送資訊、盜用賬號、購買商品、銀行轉賬,從而使你的個人隱私洩露和財產損失。


二、CSRF漏洞現狀

CSRF這種攻擊方式在2000年已經被國外的安全人員提出 ,但在國內,直到2006年才開始被關注,2008年,國內外的多個大型社群和互動網站分別爆出CSRF漏洞,如:紐約時報,Metafilter,YouTube和百度。而現在,網際網路的許多站點仍對此毫無防備,以至於安全業界稱CSRF為"沉睡的巨人"。


三、CSRF攻擊例項

聽了這麼多,可能大家還雲裡霧裡,光聽概念可能大家對於CSRF還是不夠了解,下面我將舉一個例子來讓大家對CSRF有一個更深層次的理解。

我們先假設支付寶存在CSRF漏洞,我的支付寶賬號是wl,攻擊者的支付寶賬戶是xxx,然後我們通過網頁請求的方式 http://zhifubao.com/withdraw?account=lyq&amount=10000&for=wl 可以把賬號的wl的10000元轉到我的另外一個 賬戶wl上面去。通常這個情況下,該請求傳送到支付寶伺服器後,伺服器會先驗證請求是否來自一個合法的session,並且該session的使用者已經成功登入。攻擊者在支付寶也有賬戶xxx,他直到上文中的URL可以進行轉賬操作,於是他自己可以傳送一個請求

http://zhifubao.com/withdraw?account=lyq&amount=10000&for=xxx 到支付寶後臺。但是這個請求是來自攻擊者而不是我wl,所以不能通過安全認證,因此該請求作廢。這時,攻擊者xxx想到了用CSRF的方式,他自己做了個網站,在網站中放了如下程式碼:http://zhifubao.com/withdraw?account=lyq&amount=10000&for=xxx ,並且通過網站連結誘使我來訪問他的網站。當我禁不住誘惑時就會點了進去,上述請求就會從我自己的瀏覽器傳送到支付寶,而且這個請求會附帶我的瀏覽器中的cookie。大多數情況下,該請求會失敗,因為支付寶要求我的認證資訊,但是我如果剛訪問支付寶不久,還沒有關閉支付寶頁面,我的瀏覽器中的cookie存有我的認證資訊,這個請求就會得到響應,從我的賬戶中轉10000元到xxx賬戶裡,而我絲毫不知情。


四、CSRF原理

下面簡單闡述了CSRF原理
1、使用者登入並信任網站A
2、驗證通過,在使用者處產生A的Cookie
3、使用者在沒有登出網站A的情況下,訪問危險網站B
4、網站B要求訪問第三站點網站A,發出一個request
5、根據網站B的請求,瀏覽器帶著網站A產生的Cookie訪問網站A
6、網站A不知道請求是使用者發出的還是網站B發出的,由於瀏覽器會自動帶上使用者Cookie,所以網站A會根據使用者的許可權處理請求,這樣網站B就達到了模擬使用者操作的目的

從上面步驟中可以看出,要完成一次CSRF攻擊,受害者必須依次完成以下兩個步驟:

  • 登入受信任網站A,並在本地生成Cookie
  • 在不登出A的情況下,訪問危險網站B

看到這裡,你也許會問:如果我不滿足以上兩個條件中的一個,我就不會受到CSRF攻擊。是的,確實如此,但是你不能保證以下情況不會發生

  • 你不能保證你登入了一個網站之後,不再開啟一個tab頁面並訪問其他的網站
  • 你不能保證你關閉瀏覽器之後,你本地的Cookie會立刻過期,你上次的會話已經結束
  • 上述中所謂的攻擊網站,可能就是一個釣魚網站

五、CSRF如何防禦

驗證HTTP Referer欄位

根據HTTP協議,在HTTP頭部中有一個Referer欄位,它記錄了該HTTP請求所在的地址,表示HTTP請求從哪個頁面發出的。比如當你訪問 http://zhifubao.com/withdraw?account=lyq&amount=10000&for=xxx ,使用者必須先登入支付寶網站,然後通過點選頁面的按鈕來觸發轉賬事件。此時,轉賬請求的Referer值就是轉賬頁面的所在的URL,通常是以zhifubao.com域名開頭的地址。如果攻擊者要實行CSRF攻擊,那麼他只能在自己的站點構造請求,此時Referer的值就指向黑客自己的網站。因此要防禦CSRF攻擊,支付寶只需要對每一個轉賬請求驗證其Referer值,如果是以zhifubao.com開頭的域名,則是合法請求,相反,則是非法請求並拒絕。

這種方法的好處就是簡單易行,只需要在後臺新增一個攔截器來檢查Referer即可。然而這種辦法並不是萬無一失,Referer的值是由瀏覽器提供的,一些低階的瀏覽器可以通過某種方式篡改Referer的值,這就給了攻擊者可乘之機;而一些高階瀏覽器處於安全考慮,可以讓使用者設定傳送HTTP請求時不再提供Referer值,這樣當他們正常訪問支付寶網站時,因此沒有提供Referer值而被誤認為CSRF攻擊,拒絕訪問。實際應用中通常採用第二種方法來防禦CSRF攻擊。


六、新增token驗證

CSRF攻擊之所以能夠成功,是因為攻擊者可以完全偽造使用者的請求,該請求中所有的使用者驗證資訊都存在cookie中,因此攻擊者可以在不知道這些驗證資訊的情況下直接利用使用者自己的額cookie來通過安全驗證。要防止CSRF,關鍵在於在請求中放入黑客所不能偽造的資訊,並且該資訊不存在於cookie中。可以在HTTP請求中以引數的形式假如一個隨機產生的token,並在伺服器建立一個攔截器來驗證這個token,如果請求中沒有token或者token不正確,則認為可能是Anti CSRF Token來防禦CSRF

  • 使用者訪問某個表單頁面
  • 服務端生成一個Token,放在使用者的Session中,或者瀏覽器的Cookie中
  • 在頁面表單附帶上Token引數。
  • 使用者提交請求後,服務端驗證表單中的Token是否與使用者Session(或Cookies)中的Token一致,一致為合法請求,不是則非法請求。

這個Token值必須是隨機的,不可預測的。由於Token的存在,攻擊者無法再構造一個帶有合法Token的請求實施CSRF攻擊。另外使用Token應注意Token的保密性,儘量把敏感操作由GET改成POST,以form或者AJAX形式提交,避免Token洩露。


七、驗證碼

驗證碼,強制使用者必須與應用進行互動,才能完成最終請求。通常情況下,驗證碼能夠很好的遏制CSRF攻擊。但是處於使用者體驗考慮,網站不能給所有的操作都加上驗證碼。因此驗證碼只能作為一種輔助手段。


八、儘量使用POST,限制GET

GET介面能夠直接將請求地址暴露給攻擊者,所以要防止CSRF一定最好不要用GET。當然POST並不是萬無一失,攻擊者只需要構造一個form表單就可以,但需要在第三方頁面做,這樣就增加了暴露的可能性。


九、在HTTP頭部新增自定義屬性

這種方法也是使用token並驗證,但是它是把token放在HTTP請求頭部中。通過使用AJAX我們可以在我們的請求頭部中新增我們的自定義屬性,但是這種方法要求我們將整個站的請求全部改成AJAX,如果是新站還好,老站的花無疑是個需要重寫整個站點的,這是很不可取的


十、Django Form表單和Ajax csrf設定

1、form表單設定 (只需要在表單中定義 {% csrf_token %} 即可)

<form method="post" action="{{ request.path_info }}">
    {% csrf_token %}
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="submit" value="登入" />
</form>

2、ajax單個請求設定(需要定義headers引數)

<form method="post" action="{{ request.path_info }}" id="login">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="button" value="登入" id="button" />
</form>

<script src="/static/jquery-1.12.4.js" ></script>
<script src="/static/jquery.cookie.js" ></script>
<script>
$(function(){

    $('#button').click(function(){
        $.ajax({
            url: '/wanglei/login/',
            type: 'POST',
            data: $('#login').serialize(),
            dataType: 'JSON',
            headers:  {'X-CSRFtoken': $.cookie('csrftoken')},
            success: function(data) {
                if (data.status) {
                    location.reload()
                } else {
                    alert(data.message)
                }
            }
        })
    })
})

</script>

3、ajax全域性設定(通過全域性設定,即對當前頁面所有的ajax請求都生效)

<form method="post" action="{{ request.path_info }}" id="login">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="button" value="登入" />
</form>

<script src="/static/jquery-1.12.4.js" ></script>
<script src="/static/jquery.cookie.js" ></script>
<script>
$(function(){

    /* ajax global csrf define */
    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
        }
    })
    
    $('#button1').click(function(){
        $.ajax({
            url: '/wanglei/login/',
            type: 'POST',
            data: $('#login').serialize(),
            dataType: 'JSON',
            success: function(data) {
                if (data.status) {
                    location.reload()
                } else {
                    alert(data.message)
                }
            }
        })
    })
})
</script>

十一、Django csrf 全域性生效與區域性生效

說明:django為使用者實現防止跨站請求偽造的功能,通過中介軟體 django.middleware.csrf.CsrfViewMiddleware 來完成。對於django中設定防跨站請求偽造功能有分為全域性和區域性

全域性生效

settings中的中介軟體:django.middleware.csrf.CsrfViewMiddleware

區域性生效

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

@csrf_protect:為當前函式強制設定防跨站請求偽造功能,即便settings中沒有設定全域性中介軟體
@csrf_exempt:取消當前函式防跨站請求偽造功能,即便settings中設定了全域性中介軟體


1、定於csrf全域性生效,而當前表單不生效


settings

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    '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',
]

views

from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt
def bbbb(request):
    if request.method == 'POST':
        res = {'status': True, 'data': None, 'message': None}
        try:
            bbbb1 = request.POST.get('bbbb1', None)
            bbbb2 = request.POST.get('bbbb2', None)
            if bbbb1 and bbbb2:
                pass
            else:
                res['status'] = False
                res['message'] = 'bbbb1 或 bbbb2 為空'
        except Exception as e:
            res['status'] = False
            res['message'] = '請求錯誤'
        return HttpResponse(json.dumps(res))
    elif request.method == 'GET':
        return render(request, 'bbbb.html')

bbbb.html

    <form action="{{ request.path_info }}" method="post" id="login">
        <input type="text" name="bbbb1" />
        <input type="text" name="bbbb2" />
        <input type="submit" value="提交1" />
        <input type="button" value="button1" id="button1" />
        <input type="button" value="button2" id="button2" />
    </form>

    <script src="/static/jquery-1.12.4.js" ></script>
    <script src="/static/jquery.cookie.js" ></script>
    <script>
        $(function(){

            /* button1 */
            $('#button1').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });


            /* button2 */
            $('#button2').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });
        })
    </script>



2、定於csrf全域性不生效,而當前表單生效

settings

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    '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',
]

views

@csrf_protect
def bbbb(request):
    if request.method == 'POST':
        res = {'status': True, 'data': None, 'message': None}
        try:
            bbbb1 = request.POST.get('bbbb1', None)
            bbbb2 = request.POST.get('bbbb2', None)
            if bbbb1 and bbbb2:
                pass
            else:
                res['status'] = False
                res['message'] = 'bbbb1 或 bbbb2 為空'
        except Exception as e:
            res['status'] = False
            res['message'] = '請求錯誤'
        return HttpResponse(json.dumps(res))
    elif request.method == 'GET':
        return render(request, 'bbbb.html')

3、html

    <form action="{{ request.path_info }}" method="post" id="login">
        {% csrf_token %}
        <input type="text" name="bbbb1" />
        <input type="text" name="bbbb2" />
        <input type="submit" value="提交1" />
        <input type="button" value="button1" id="button1" />
        <input type="button" value="button2" id="button2" />
    </form>

    <script src="/static/jquery-1.12.4.js" ></script>
    <script src="/static/jquery.cookie.js" ></script>
    <script>
        $(function(){

            /* ajax global csrf */
            $.ajaxSetup({
                beforeSend: function(xhr, settings){
                    xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
                }
            });

            /* button1 */
            $('#button1').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    headers: {'X-CSRFtoken': $.cookie('csrftoken')},
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });


            /* button2 */
            $('#button2').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });
        })
    </script>

十二、定義不需要CSRF防跨站請求的方法

bbbb.html

    <form action="{{ request.path_info }}" method="post" id="login">
        {% csrf_token %}
        <input type="text" name="bbbb1" />
        <input type="text" name="bbbb2" />
        <input type="submit" value="提交1" />
        <input type="button" value="button1" id="button1" />
        <input type="button" value="button2" id="button2" />
    </form>

    <script src="/static/jquery-1.12.4.js" ></script>
    <script src="/static/jquery.cookie.js" ></script>
    <script>
        $(function(){
        
            function csrfSafeMethod(method) {
                // these HTTP methods do not require CSRF protection
                return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
            }
            
            /* ajax global csrf */
            $.ajaxSetup({
                beforeSend: function(xhr, settings){
                    xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
                }
            });

            /* button1 */
            $('#button1').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    headers: {'X-CSRFtoken': $.cookie('csrftoken')},
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });


            /* button2 */
            $('#button2').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });
        })
    </script>