注意安全(2)!XSRF跨站偽造請求

上一篇我們說了用腳本注入去公雞小熊哥的博文評論。普通的腳本注入很容易就被前端后臺全方位的腳本過濾給處理掉,完全沒有殺傷力。一般來說遇到<, > , =" 等等可能加載執行腳本的用戶輸入,轉換為&lt ; & gt ; 等等即可作為數據打印到 DOM 中,而不是作為腳本執行。詳細情況參考上一篇文章 。我敢保證,@Swanky 肯定是受了我的蠱惑去 POST 了 三萬多條評論導致被 @簡書首席封號官 封號了。

偽造請求的過程


這次來說說偽造公雞(Cross Site Request Forgery)的事情。通俗點講就是 有人給你發送了一個鏈接,然后你手賤點開,是看起來很正常的網頁(A.com/index.html),結果暗中被發送了一個指向 招商銀行轉賬服務(B.com/transfer) 的POST請求,這個請求是攻擊者在A.com/index.html 腳本中偽造的,因此請求的發送方屬于A.com域,而不屬于B.com/transfer 的域, 所以叫做 跨站偽造請求

// attack.html. 為了在這個偽造的網頁中自動發送請求并且帶上 受害用戶的cookie,
// 一般都會在hidden iframe中加載一份正規網站的頁面,而且攻擊表單也是隱藏的。
// 表面上這個偽造網頁是很正規的,并無異常。
  <form id="thisForm" method="POST" target="_blank" action="http://www.bank.com/transfer">
        <input type="text" name="name" value="csrf_Attack2!!!">
  </form>
  <iframe name="tab" src="http://www.bank.com" style="display:none;"> 
  </iframe> 

XSRF 原理


XSRF的關鍵就是 假如正規網站(B.com)的服務器端沒有防御機制,只是對于發來的請求做用戶身份驗證,那么偽造請求就可以借用受害者瀏覽器自動發送的cookie蒙混過關,實行惡意攻擊。其中有幾個問題需要明確:

  1. 用戶身份信息一般通過Http Request 的頭部cookie 字段的值來傳遞,cookie中往往包含sessionid 這樣的用戶會話信息,服務器通過比對服務端內存或數據庫(Redis等)中維持的session就可以知道是哪個用戶在發請求,Session有無過期。 這一過程一般不驗證請求發送方的 域。
  2. 跨站偽造請求成功的前提是 B.com 提供了可供跨域調用的服務。因為我們知道大部分瀏覽器和服務器都禁止腳本請求跨域資源,這種行為一般不太安全。但是我非得請求呢?? 所以除了用JSONP 來GET跨域資源,更有采用CORS(Cross-origin resource sharing,跨域資源共享是W3C標準)來實現POST,PUT請求跨域的。 CORS的詳細介紹可以參考這里.

通常情況下,假如服務器端開啟CORS配置允許任何來源的請求,就很難分辨接收到的請求是用戶自主發送的(往往是和B.com同一個域下的網頁),還是攻擊者冒充用戶身份偽造的攻擊請求(往往不同域,例如A.com)。

一休哥自制 XSRF 圖示

XSRF防御——Token


經過上面的介紹,其實防御的辦法大概都可以推斷出來了。關鍵就是區分來自于各種域的請求,哪些是合法用戶發送的,哪些是被借用了身份發送來的。

  1. 配置服務器的CORS,只允許白名單的域發來請求,這樣最直接。只要白名單的網站不被攻擊注入惡意腳本就基本安全了。以nodejs和express為例,配置如下:
app.all('*', function(req, res, next) {
   // 如果用"*", 表示接受任何域發來的請求, 這里只配置一個域名
   res.header("Access-Control-Allow-Origin", "www.icbc.com");
   res.header("Access-Control-Allow-Headers", "X-Requested-With, accept, origin, content-type");
   res.header("Access-Control-Allow-Methods", "PUT,POST,OPTIONS");
   next();
}

2.bank.com 的服務器端和客戶端通力合作,協商一個除了Session之外的二次身份驗證——Token。

Token驗證的原理是:每次用戶登陸成功,就根據SessionID特定算法生成一個Token,發回客戶端(動態添加到一個隱藏域中,例如<input> 或者JS變量中),合法客戶端在隨后的請求中加入Token作為數據體,服務器端每次接受請求都要去驗證SessionID 和 Token,雙保險。以nodejs為例,一休哥自制服務端關鍵代碼:

// 當用戶登錄驗證成功時,隨即生成與該用戶Session綁定的Token
req.session.key = (Math.random()* Math.pow(10, 17));
var crsfToken = generateToken(parseInt(req.session.key).toString());
// 返回token給客戶端,客戶端將token動態添加至DOM或者變量中
res.send({"status": returnData, "refreshpage": redirectpage, "token": crsfToken});

// return Hex string as token. 
// 加密算法遠比這個復雜。這里只是演示,這樣也很難猜出token
function generateToken(sess) {
  var tk = "", a = 0.834415, b = 1.214414, c = 1.83121;
  for(var i=0; i < sess.length; i++){
    var baseNum = parseInt(sess[i]);
    tk += (a * Math.pow(baseNum, b) + c).toFixed(0);
  }
  var tkNum = parseInt(tk);
  return tkNum.toString(16);
}

// 當收到請求后,除了驗證session,再根據session計算一次token,比對客戶端發來的是否一致。
// check Token to prevent XSRF... 
function checkToken(session, params) {
  var validToken = generateToken(session.name);
  if (params.token && params.token == validToken) {
    return true;
  } else {
    console.error("CSRF Token not valid! Attack Found !!");
    return false;
  }
}

再補充一點,因為冒充身份的請求一般都是趁受害者處于登錄狀態了,所以DOM中沒有Token,而經過合法登錄后才會有Token存在。這樣就較好地防御了跨站偽造請求攻擊。

寫在最后

現如今,網絡安全形勢越來越嚴峻,作為一個Web前端工程師,之前很少注意這一點。至此已經更新了兩篇 跟前端工程師相關的網絡安全 筆記了。希望大家喜歡本文,有所收獲。

傳送門:注意安全第一篇 XSS 跨站腳本注入

另外一篇很棒的英文參考 CSRF Attacks, XSRF or Sea-Surf – What They Are and How to Defend Against Them

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容