CI的CSRF防範原理及注意事項

首先我們談談什麼是CSRF,它就是Cross-Site Request Forgery跨站請求僞造的簡稱。很多開發者甚至不夠重視這個問題,認爲這不是安全漏洞,而不過是惡意訪問而已,它的攻擊原理我在這裏簡單地描述一下:

有一天你打開你簡單優雅逼格十足的谷歌瀏覽器,首先打開了一個tab頁,登錄並訪問了你的微博首頁。我們這裏假設weibo.cn有這樣一個網址: http://www.weibo.cn?follow_uid=123 ,意思是關注id爲123的一個用戶。這是一個正常的地址,訪問也沒有問題。
緊接着你的QQ羣裏發來了一個讓你感到好奇的鏈接,http://www.comeonbaby.com, 你禁不住誘惑打開了這個鏈接,並在瀏覽器裏的另一個tab頁裏顯示出來。緊接着,你打開你的微博tab頁,發現無故關注了一個新的用戶。咦,這是爲何?

原因很簡單,很可能在你打開的http://www.comeonbaby.com鏈接裏存在着這樣一個html元素: <img src="http://www.weibo.cn?follow_uid=123" alt="" />, 瀏覽器試圖加載這個img,很顯然加載失敗了,因爲它不是一個有效的圖片格式。但是,這個請求依然被髮送出去了,此時你的微博是登錄狀態中,然後,你就真的follow了123, 你看,你被強姦了。

這就是簡單的csrf攻擊。

在實際的網站項目中,如: http://www.abc.com/logout之類的鏈接都應該注意,註銷類的、刪除內容類的、轉賬類的都可能中埋伏,輕則讓你感到詫異,重則數據丟失,財產損失,所以要重視任何一個對數據有操作行爲的url。那麼我們在CI裏如何解決呢?
簡單地:
第一步: 在application/config/config.php裏配置以下字段:

 
$config['csrf_protection'] = true;
$config['csrf_token_name'] = 'csrf_token_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
 

第二步: 在form裏使用form_open(),幫助生成一個token。

接下來我說一下csrf的工作原理:

簡單地來說,當我們訪問一個頁面如: http://www.abc.com/register時, CI會生成一個名爲csrf_cookie_name的cookie,其值爲hash,併發送到客戶端。同時由於你在該頁面裏使用了form_open(),會在form標籤下生成一個<input type="hidden" name="csrf_token_name" value="12uffu2910"/>之類的隱藏字段,其值也爲hash。

緊接着用戶點擊了註冊按鈕,瀏覽器將這些數據包括csrf_token_name發送到(post到)服務器,同時也會將名爲csrf_cookie_nam的cookie發送回去。服務器會比較csrf_token_name的值(也就是hash) 與 csrf_cookie_name 的cookie值(同樣也是hash)是否相同, 如果相同則通過,如果不同則說明是csrf攻擊。

接下來我們分析一下CI的源代碼:

CI在Codeigniter.php裏會先加載Security類,接着加載Input類,這兩個類在每次訪問時都會自動加載的。

先加載 Security類,該類的初始方法首先設置一個hash, 這個hash如果爲空,則會在cookie裏檢查是否存在,如果存在則設爲hash;否則會計算出一個新的hash。  

接下來開始初始化Input類,導致調用$this->security->csrf_verify()方法。該方法首先判斷該請求是否爲post請求,如果不是,則會設置一個名爲csrf_cookie_name的cookie,其值爲hash,如果是post請求,則會用post過來的csrf_token值與csrf_cookie值比較,比較失敗則輸出錯誤;成功則會刪除csrf_tookie的值,再次設置csrf_hash的值(同上,先檢查cookie,此時爲空,則會新計算一個hash),緊接着又重新賦予了新的csrf_cookie值。

在實際操作過程中, 如有一個register的視圖,其頁面必然後 form_open()的調用,該方法會產生一個 csrf_token的 hash值, 當post到一個 action時, 該action自然就會執行檢查。

由上可以知道:
(1)如果開啓了csrf保護,每次調用都會生成一個叫csrf_cookie_name的cookie, 並將值設爲hash;
(2)直到遇到一次post請求時纔會將以前的cookie刪除,重新生成一個hash, 如此反覆。

但是,…………

細心的讀者可能發現了, 我上文中舉的例子是get請求,而CI的csrf只是設計了post請求的防範策略,那麼請你想想,你在你的項目中是否存在着 get請求的 資源操作url地址呢?你是否對這樣的url地址進行過csrf防範?

我們的建議:
(1)重要的資源操作,都儘量採取post請求,防止csrf攻擊;
(2)如果你執意使用get請求,也不是沒有辦法,原理跟上面也是類似的,比如上文提到的關注賬號的操作,你可以設計這樣一個地址:
http://www.weibo.cn?follow_uid=123&token=73ksdkfu102
token是什麼?是你隨機產生的一個字符串,等用戶發送回去後你依然做驗證,如果驗證通過,則執行後續的關注操作,如果沒通過,我們就認爲該操作是不合法的。 那個誘惑你的攻擊者不可能知道每個人的token, 即使你點擊了那個鏈接,依然不會被認爲是有效的訪問地址。

一點建議:
由於CI開啓csrf保護是全局性的,這樣就會導致你的任何post請求都需要加入csrf_token_name的數據字段,的確非常繁瑣,有些人索性就關閉了。在這裏給出三個解決方法:
(1)每個form裏都加入這樣一個傳遞數據:

 $.post(url, {'<?php echo $this->security->get_csrf_token_name(); ?>' : '<?php echo $this->security->get_csrf_hash(); ?>'}, function(){});

(2)爲ajax請求加入全局傳遞數據:

//
$(function($) {
    // this script needs to be loaded on every page where an ajax POST may happen
    $.ajaxSetup({
        data: {
            '<?php echo $this->security->get_csrf_token_name(); ?>' : '<?php echo $this->security->get_csrf_hash(); ?>'
        }
    });
  // now write your ajax script
});

(3)自己寫一個helper方法,直接在view中使用,加入隱藏字段,如果你不喜歡使用form_open()的話:

    function csrf_hidden(){
     $ci = &get_instance();
     $name = $ci->security->get_csrf_token_name();
     $val = $ci->security->get_csrf_hash();
     echo "<input type=\"hidden\" name=\"$name\" value=\"$val\" />";
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章