CSRF,Cross Site Request Forgery,即跨網站請求偽造。
這種攻擊是指,在使用者正常登入系統以後,攻擊者誘使使用者訪問一些非法連結,以執行一些非法操作。比如:如果刪除使用者操作(如,yourdomain.com/deluser?id=123)沒有經過防範CSRF的處理,那麼,假設使用者登入系統後,攻擊者誘使使用者同時訪問了攻擊者的網站的一個連結(該連結正好為yourdomain.com/deluser?id=123),那麼,系統就會在使用者不知情的情況下丟失一個使用者。
在這個例子中,跨站請求中的連結之所以被正常執行,首先是因為請求中瀏覽器正常發送了yourdomain的認證資訊(一般儲存在cookie中),伺服器根本不知道該請求是使用者為之,還是惡意為之。其次,就是請求中的參數是可以被猜測的。這兩個條件,構成了CSRF攻擊的全部條件。
這裡還需要強調一下,如果認證基於cookie,那麼實際上還有第三個條件:如果cookie是本地cookie,瀏覽器還需要允許跨域發送本地cookie,即如果請求是第三方網站發起的,應帶上請求的域的cookie。IE預設是不允許跨域發送本地cookie的(session cookie則無此限制),而firefoxe就預設允許的。
原理如圖:
應對策略
1:採用token的形式。
採用token是指讓請求所帶的參數變的不可猜測。即,每次需要保護的請求都要帶上一個額外的參數,該參數可以是sessionid(一定要是額外的參數,但是其值可以為sessionid),也可以是另外的無法被猜測的一個值。然後,伺服器在得到這個請求後,再驗證該值是否匹配。
可能有人會進一步提出,不是sessionid也是可以非法得到的嗎,或者說使用者的sessionid是沒有授權被操作的?答案是沒錯,但是,那又是另外的攻擊方法(涉及到工作階段劫持和許可權欺騙)了,在這裡,僅僅防禦的是CSRF攻擊。
不過為了保險起見,我們可以用sessionid+salt,然後散列的方式來產生這個token。
採用token的形式,我們還需要考慮該token,也就是用戶端所帶的這個參數的儲存問題。從CSRF的本質考慮,token的儲存首先不能儲存在cookie中,因為cookie本身就是在發送請求的時候可以被帶上。
其次,token可以儲存在伺服器端嗎,如,我們可以為當前請求設定一個唯一標識,然後儲存在session中。答案當然也是不行的,我們可以假設完成一次請求包含兩個部分:發起請求的URL(或程式),處理請求的URL(或程式),誠然,這種方式我們防住了單獨請求”處理請求的URL”的CSRF攻擊。但是,既然攻擊者得到了處理請求的頁面,那麼,他在偽造CSRF的時候,只要帶上了發送請求的頁面,就依然可以完成一次攻擊。
所以,token的儲存只能是儲存在發送到用戶端的頁面中,然後用戶端在接下來發送的請求的時候,帶上這個參數就可以了。當然,如果頁面本身已經被XSS攻破,那麼攻擊者仍舊可以偽造一次合法請求,但這已經不是防範CSRF的範疇了,而是防範XSS。
2:每次需要被保護的請求發送時,都要求使用者輸入密碼;
3:每次需要被保護的請求發送時,都帶上referrer。不過這並不是應對的最佳策略,因為referrer是可以被輕易偽造的。
具體措施
以下具體措施針對token的形式。
n 遍曆前台所有發送請求的地方
1:檔案尋找前台所有的”svc”,”ajax”,”.aspx”,”.html”,”.htm”
2:檔案尋找前台所有的”form”
根據以上的尋找,匯總到如下的表格:
n 處理請求
篩選出需要進行CSRF處理的請求。然後對請求做如下處理:
如果是GET方式發送的請求,則為請求加入參數token=[value],其中[value]為sessionid的值;
如果是POST方式發送的請求,則為Form加入隱藏的input,其name為token,其值為sessionid。
n 遍曆所有的請求處理處
1:遍曆所有的svc,為svc的方法增加token參數
2:遍曆所有的aspx頁面的code-behind
3:遍曆所有其它的後台方法,如果存在的話,如控制器方法(在EL中並不存在)。
根據以上的尋找,匯總到如下的表格
n 處理請求處理處
處理參數中的token,檢測該token是否存在於當前的sessionid中,如果存在,則允許存取,否則異常;
以上全部的邏輯用代碼錶示,大致如下
| 代碼如下 |
複製代碼 |
protected void Page_Load(object sender, EventArgs e) { string token = CreateToken(); PutTokenToClient(token); SaveTokenInServer(token); } protected void ButtonDosomething_Click(object sender, EventArgs e) { string token = GetTokenFromRequest(); //需要csrf保護的地方就check才允許存取 if (TokenIsOK(token)) { //todo: go } else { //todo: block } } private string GetTokenFromRequest() { //todo 從請求中得到coken,一般為URL QueryString或表單元素 throw new NotImplementedException(); } private void PutTokenToClient(string token) { //todo 將其儲存到前台,如請求的url,或隱藏的input } private void SaveTokenInServer(string token) { //一般儲存在session中 Session["CRSFToken"] = token; } private bool TokenIsOK(string token) { string tokenInServer = Session["CRSFToken"].ToString(); return tokenInServer == token ? true : false; } public string _salt = "asdfkl@,.;#sss13131313"; public string CreateToken() { return MD5(Session.SessionID + _salt); } private void ClearToken() { Session["CRSFToken"] = string.Empty; } private string MD5(string p) { throw new NotImplementedException(); } |