跨站請求僞造漏洞

首先說明一下什麼是CSRF(Cross Site Request Forgery)?php

跨站請求僞造是指攻擊者能夠在第三方站點製造HTTP請求並以用戶在目標站點的登陸態發送到目標站點,而目標站點未校驗請求來源使第三方成功僞造請求。html

爲何會有CSRF?web

JS控制瀏覽器發送請求的時候,瀏覽器是根據目標站點,而不是來源站點,來發送cookie的,若是當前會話中有目標站點的cookie,就發送出去。核心問題是瀏覽器的會話機制,是跨站請求僞造漏洞的根源。算法

 

CSRF(Cross-site request forgery跨站請求僞造,也被稱成爲「one click attack」或者session riding,一般縮寫爲CSRF或者XSRF,是一種對網站的惡意利用。瀏覽器


解決方法有三種:安全

1 使用token服務器

2 限制refercookie

3 使用驗證碼技術session

 

 

1、CSRF攻擊原理併發

 

CSRF攻擊原理比較簡單,如圖1所示。其中Web A爲存在CSRF漏洞的網站,Web B爲攻擊者構建的惡意網站,User C爲Web A網站的合法用戶。

 

跨站請求僞造CSRF防禦方法

 

圖1 CSRF攻擊原理

 

1. 用戶C打開瀏覽器,訪問受信任網站A,輸入用戶名和密碼請求登陸網站A;

 

2.在用戶信息經過驗證後,網站A產生Cookie信息並返回給瀏覽器,此時用戶登陸網站A成功,能夠正常發送請求到網站A;

 

3. 用戶未退出網站A以前,在同一瀏覽器中,打開一個TAB頁訪問網站B;

 

4. 網站B接收到用戶請求後,返回一些攻擊性代碼,併發出一個請求要求訪問第三方站點A;

 

5. 瀏覽器在接收到這些攻擊性代碼後,根據網站B的請求,在用戶不知情的狀況下攜帶Cookie信息,向網站A發出請求。網站A並不知道該請求實際上是由B發起的,因此會根據用戶C的Cookie信息以C的權限處理該請求,致使來自網站B的惡意代碼被執行。

 

2、CSRF漏洞防護

 

CSRF漏洞防護主要能夠從三個層面進行,即服務端的防護、用戶端的防護和安全設備的防護。

 

一、 服務端的防護

 

.1.1 驗證HTTP Referer字段

 

根據HTTP協議,在HTTP頭中有一個字段叫Referer,它記錄了該HTTP請求的來源地址。在一般狀況下,訪問一個安全受限頁面的請求必須 來自於同一個網站。好比某銀行的轉帳是經過用戶訪問http://bank.test/test?page=10&userID=101& amp;money=10000頁面完成,用戶必須先登陸bank.test,而後經過點擊頁面上的按鈕來觸發轉帳事件。當用戶提交請求時,該轉帳請求的 Referer值就會是轉帳按鈕所在頁面的URL(本例中,一般是以bank. test域名開頭的地址)。而若是攻擊者要對銀行網站實施CSRF攻擊,他只能在本身的網站構造請求,當用戶經過攻擊者的網站發送請求到銀行時,該請求的 Referer是指向攻擊者的網站。所以,要防護CSRF攻擊,銀行網站只須要對於每個轉帳請求驗證其Referer值,若是是以bank. test開頭的域名,則說明該請求是來自銀行網站本身的請求,是合法的。若是Referer是其餘網站的話,就有多是CSRF攻擊,則拒絕該請求。

 

1.2 在請求地址中添加token並驗證

 

CSRF攻擊之因此可以成功,是由於攻擊者能夠僞造用戶的請求,該請求中全部的用戶驗證信息都存在於Cookie中,所以攻擊者能夠在不知道這些驗 證信息的狀況下直接利用用戶本身的Cookie來經過安全驗證。由此可知,抵禦CSRF攻擊的關鍵在於:在請求中放入攻擊者所不能僞造的信息,而且該信息 不存在於Cookie之中。鑑於此,系統開發者能夠在HTTP請求中以參數的形式加入一個隨機產生的token,並在服務器端創建一個攔截器來驗證這個 token,若是請求中沒有token或者token內容不正確,則認爲多是CSRF攻擊而拒絕該請求。

 

1.3 在HTTP頭中自定義屬性並驗證

 

自定義屬性的方法也是使用token並進行驗證,和前一種方法不一樣的是,這裏並非把token以參數的形式置於HTTP請求之中,而是把它放到 HTTP頭中自定義的屬性裏。經過XMLHttpRequest這個類,能夠一次性給全部該類請求加上csrftoken這個HTTP頭屬性,並把 token值放入其中。這樣解決了前一種方法在請求中加入token的不便,同時,經過這個類請求的地址不會被記錄到瀏覽器的地址欄,也不用擔憂 token會經過Referer泄露到其餘網站。

 

二、 其餘防護方法

 

1. CSRF攻擊是有條件的,當用戶訪問惡意連接時,認證的cookie仍然有效,因此當用戶關閉頁面時要及時清除認證cookie,對支持TAB模式(新標籤打開網頁)的瀏覽器尤其重要。

 

2. 儘可能少用或不要用request()類變量,獲取參數指定request.form()仍是request. querystring (),這樣有利於阻止CSRF漏洞攻擊,此方法只不能徹底防護CSRF攻擊,只是必定程度上增長了攻擊的難度。

 

代碼示例:

 

Java 代碼示例

 

下文將以 Java 爲例,對上述三種方法分別用代碼進行示例。不管使用何種方法,在服務器端的攔截器必不可少,它將負責檢查到來的請求是否符合要求,而後視結果而決定是否繼 續請求或者丟棄。在 Java 中,攔截器是由 Filter 來實現的。咱們能夠編寫一個 Filter,並在 web.xml 中對其進行配置,使其對於訪問全部須要 CSRF 保護的資源的請求進行攔截。

 

在 filter 中對請求的 Referer 驗證代碼以下

 

清單 1. 在 Filter 中驗證 Referer

 

 // 從 HTTP 頭中取得 Referer 值 String referer=request.getHeader("Referer"); // 判斷 Referer 是否以 bank.example 開頭 if((referer!=null) &&(referer.trim().startsWith(「bank.example」))){ chain.doFilter(request, response); }else{ request.getRequestDispatcher(「error.jsp」).forward(request,response); }

 

以上代碼先取得 Referer 值,而後進行判斷,當其非空並以 bank.example 開頭時,則繼續請求,不然的話多是 CSRF 攻擊,轉到 error.jsp 頁面。

 

若是要進一步驗證請求中的 token 值,代碼以下

 

清單 2. 在 filter 中驗證請求中的 token

 

HttpServletRequest req = (HttpServletRequest)request; HttpSession s = req.getSession(); // 從 session 中獲得 csrftoken 屬性 String sToken = (String)s.getAttribute(「csrftoken」); if(sToken == null){ // 產生新的 token 放入 session 中 sToken = generateToken(); s.setAttribute(「csrftoken」,sToken); chain.doFilter(request, response); } else{ // 從 HTTP 頭中取得 csrftoken String xhrToken = req.getHeader(「csrftoken」); // 從請求參數中取得 csrftoken String pToken = req.getParameter(「csrftoken」); if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ chain.doFilter(request, response); }else if(sToken != null && pToken != null && sToken.equals(pToken)){ chain.doFilter(request, response); }else{ request.getRequestDispatcher(「error.jsp」).forward(request,response); } }

 

首先判斷 session 中有沒有 csrftoken,若是沒有,則認爲是第一次訪問,session 是新創建的,這時生成一個新的 token,放於 session 之中,並繼續執行請求。若是 session 中已經有 csrftoken,則說明用戶已經與服務器之間創建了一個活躍的 session,這時要看這個請求中有沒有同時附帶這個 token,因爲請求可能來自於常規的訪問或是 XMLHttpRequest 異步訪問,咱們分別嘗試從請求中獲取 csrftoken 參數以及從 HTTP 頭中獲取 csrftoken 自定義屬性並與 session 中的值進行比較,只要有一個地方帶有有效 token,就斷定請求合法,能夠繼續執行,不然就轉到錯誤頁面。生成 token 有不少種方法,任何的隨機算法均可以使用,Java 的 UUID 類也是一個不錯的選擇。

 

除了在服務器端利用 filter 來驗證 token 的值之外,咱們還須要在客戶端給每一個請求附加上這個 token,這是利用 js 來給 html 中的連接和表單請求地址附加 csrftoken 代碼,其中已定義 token 爲全局變量,其值能夠從 session 中獲得。

 

清單 3. 在客戶端對於請求附加 token

 

function appendToken(){ updateForms(); updateTags(); } function updateForms() { // 獲得頁面中全部的 form 元素 var forms = document.getElementsByTagName('form'); for(i=0; i<forms.length; i++) { var url = forms[i].action; // 若是這個 form 的 action 值爲空,則不附加 csrftoken if(url == null || url == "" ) continue; // 動態生成 input 元素,加入到 form 以後 var e = document.createElement("input"); e.name = "csrftoken"; e.value = token; e.type="hidden"; forms[i].appendChild(e); } } function updateTags() { var all = document.getElementsByTagName('a'); var len = all.length; // 遍歷全部 a 元素 for(var i=0; i<len; i++) { var e = all[i]; updateTag(e, 'href', token); } } function updateTag(element, attr, token) { var location = element.getAttribute(attr); if(location != null && location != '' '' ) { var fragmentIndex = location.indexOf('#'); var fragment = null; if(fragmentIndex != -1){ //url 中含有隻至關頁的錨標記 fragment = location.substring(fragmentIndex); location = location.substring(0,fragmentIndex); } var index = location.indexOf('?'); if(index != -1) { //url 中已含有其餘參數 location = location + '&csrftoken=' + token; } else { //url 中沒有其餘參數 location = location + '?csrftoken=' + token; } if(fragment != null){ location += fragment; } element.setAttribute(attr, location); } } 

 

在客戶端 html 中,主要是有兩個地方須要加上 token,一個是表單 form,另外一個就是連接 a。這段代碼首先遍歷全部的 form,在 form 最後添加一隱藏字段,把 csrftoken 放入其中。而後,代碼遍歷全部的連接標記 a,在其 href 屬性中加入 csrftoken 參數。注意對於 a.href 來講,可能該屬性已經有參數,或者有錨標記。所以須要分狀況討論,以不一樣的格式把 csrftoken 加入其中。

 

若是你的網站使用 XMLHttpRequest,那麼還須要在 HTTP 頭中自定義 csrftoken 屬性,利用 dojo.xhr 給 XMLHttpRequest 加上自定義屬性代碼以下:

 

清單 4. 在 HTTP 頭中自定義屬性

 

var plainXhr = dojo.xhr;// 重寫 dojo.xhr 方法 dojo.xhr = function(method,args,hasBody) { // 確保 header 對象存在 args.headers = args.header || {};  tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>'; var token = dojo.getObject("tokenValue");  // 把 csrftoken 屬性放到頭中 args.headers["csrftoken"] = (token) ? token : " "; return plainXhr(method,args,hasBody); };

 

這裏改寫了 dojo.xhr 的方法,首先確保 dojo.xhr 中存在 HTTP 頭,而後在 args.headers 中添加 csrftoken 字段,並把 token 值從 session 裏拿出放入字段中。

 

PHP代碼示例:

 

請看下面一個簡單的應用,它容許用戶購買鋼筆或鉛筆。界面上包含下面的表單:

 

<form action="buy.php" method="POST"> <p> Item: <select name="item"> <option name="pen">pen</option> <option name="pencil">pencil</option> </select><br /> Quantity: <input type="text" name="quantity" /><br /> <input type="submit" value="Buy" /> </p></form>

 

下面的buy.php程序處理表單的提交信息:

 

<?php session_start(); $clean = array(); if (isset($_REQUEST['item'] && isset($_REQUEST['quantity'])) { /* Filter Input ($_REQUEST['item'], $_REQUEST['quantity']) */ if (buy_item($clean['item'], $clean['quantity'])) { echo '<p>Thanks for your purchase.</p>'; } else { echo '<p>There was a problem with your order.</p>'; } }?>

 

攻擊者會首先使用這個表單來觀察它的動做。例如,在購買了一支鉛筆後,攻擊者知道了在購買成功後會出現感謝信息。注意到這一點後,攻擊者會嘗試經過訪問下面的URL以用GET方式提交數據是否能達到一樣的目的:

 

http://store.example.org/buy.php?item=pen&quantity=1

 

若是能成功的話,攻擊者如今就取得了當合法用戶訪問時,能夠引起購買的URL格式。在這種狀況下,進行跨站請求僞造攻擊很是容易,由於攻擊者只要引起受害者訪問該URL便可。

 

請看下面對前例應用更改後的代碼:

 

php
  session_start(); $token = md5(uniqid(rand(), TRUE)); $_SESSION['token'] = $token; $_SESSION['token_time'] = time();?>

 

表單:

 

<form action="buy.php" method="POST"> <input type="hidden" name="token" value="<?php echo $token; ?>" /> <p> Item: <select name="item"> <option name="pen">pen</option> <option name="pencil">pencil</option> </select><br /> Quantity: <input type="text" name="quantity" /><br /> <input type="submit" value="Buy" /> </p></form>

 

經過這些簡單的修改,一個跨站請求僞造攻擊就必須包括一個合法的驗證碼以徹底模仿表單提交。因爲驗證碼的保存在用戶的session中的,攻擊者必 須對每一個受害者使用不一樣的驗證碼。這樣就有效的限制了對一個用戶的任何攻擊,它要求攻擊者獲取另一個用戶的合法驗證碼。使用你本身的驗證碼來僞造另一 個用戶的請求是無效的。

 

該驗證碼能夠簡單地經過一個條件表達式來進行檢查:

 

<?php if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token']) { /* Valid Token */ }?>

 

你還能對驗證碼加上一個有效時間限制,如5分鐘:

 

<?php $token_age = time() - $_SESSION['token_time']; if ($token_age <= 300) { /* Less than five minutes has passed. */ }?>

 

經過在你的表單中包括驗證碼,你事實上已經消除了跨站請求僞造攻擊的風險。能夠在任何須要執行操做的任何表單中使用這個流程。

 

 

http://netsecurity.51cto.com/art/201308/407554.htm

http://netsecurity.51cto.com/art/201104/256035.htm

相關文章
相關標籤/搜索