1. 程式人生 > >【轉】驗證HTTP Referer欄位

【轉】驗證HTTP Referer欄位

CSRF(Cross-site request forgery跨站請求偽造,也被稱成為“one click attack”或者session riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。

1 CSRF攻擊原理

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

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漏洞防禦主要可以從三個層面進行,即服務端的防禦、使用者端的防禦和安全裝置的防禦。

2.1      服務端的防禦

2.1.1  驗證HTTP Referer欄位

根據HTTP協議,在HTTP頭中有一個欄位叫Referer,它記錄了該HTTP請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求必須來自於同一個網站。比如某銀行的轉賬是通過使用者訪問http://bank.test/test?page=10&userID=101&money=10000頁面完成,使用者必須先登入bank.test,然後通過點選頁面上的按鈕來觸發轉賬事件。當用戶提交請求時,該轉賬請求的Referer值就會是轉賬按鈕所在頁面的URL(本例中,通常是以bank. test域名開頭的地址)。而如果攻擊者要對銀行網站實施CSRF攻擊,他只能在自己的網站構造請求,當用戶通過攻擊者的網站傳送請求到銀行時,該請求的Referer是指向攻擊者的網站。因此,要防禦CSRF攻擊,銀行網站只需要對於每一個轉賬請求驗證其Referer值,如果是以bank. test開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果Referer是其他網站的話,就有可能是CSRF攻擊,則拒絕該請求。

2.1.2 在請求地址中新增token並驗證

CSRF攻擊之所以能夠成功,是因為攻擊者可以偽造使用者的請求,該請求中所有的使用者驗證資訊都存在於Cookie中,因此攻擊者可以在不知道這些驗證資訊的情況下直接利用使用者自己的Cookie來通過安全驗證。由此可知,抵禦CSRF攻擊的關鍵在於:在請求中放入攻擊者所不能偽造的資訊,並且該資訊不存在於Cookie之中。鑑於此,系統開發者可以在HTTP請求中以引數的形式加入一個隨機產生的token,並在伺服器端建立一個攔截器來驗證這個token,如果請求中沒有token或者token內容不正確,則認為可能是CSRF攻擊而拒絕該請求。

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

自定義屬性的方法也是使用token並進行驗證,和前一種方法不同的是,這裡並不是把token以引數的形式置於HTTP請求之中,而是把它放到HTTP頭中自定義的屬性裡。通過XMLHttpRequest這個類,可以一次性給所有該類請求加上csrftoken這個HTTP頭屬性,並把token值放入其中。這樣解決了前一種方法在請求中加入token的不便,同時,通過這個類請求的地址不會被記錄到瀏覽器的位址列,也不用擔心token會通過Referer洩露到其他網站。

3.3      其他防禦方法

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 Referer

CSRF(Cross-site request forgery跨站請求偽造,也被稱成為“one click attack”或者session riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。 1 CSRF攻擊原理 CSRF攻擊原理比較簡單,如圖1所示。其中Web A為存在CS

關於HTTP中文翻譯的討論

esp 企業應用 郵件 上架 網絡 問號 相對 領導 ie6 http://www.ituring.com.cn/article/1817 討論參與者共16位: 圖靈謝工 楊博 陳睿傑 賈洪峰 李錕 丁雪豐 郭義 梁濤 吳璽喆 鄧聰 胡金埔 臧秀濤 張伸

通過HTTP服務訪問FTP伺服器檔案(配置nginx+ftp伺服器)

1.前提     已安裝配置好nginx+ftp服務 2.配置Nginx 伺服器     2.1進入nginx 配置檔案目錄: cd  /usr/local/nginx/conf vim  nginx.conf         2.2 修改配置檔案:有兩種

stream處理含null的排序

msgInfoList=msgInfoList.stream().sorted(Comparator.comparing(l->l.getCreateDate(), Comparator.nullsFirst(java.util.Date::compareTo).reversed())).

4. Java反射——

 ========================================================================================      使用java反射,你可以在執行時檢查類的欄位(成員變數)並且get/set它們的值。這些是通過Java類java

innosetup教程2如何通過[code]自定義安裝介面

目標要求:        1、 介面border去掉原本windows自帶的對話方塊格式,採取扁平化設計;        2、 簡化安裝流程,不要彈出那麼多安裝嚮導頁,不要讓使用者一直點“下一步”,簡潔人性化;        3、 安裝介面可載入漂亮的背景圖片;    

sql根據兩個的值組合情況去自定義第三個

場景2: 根據兩個欄位的值組合情況去自定義第三個欄位 原始表: S_INFO_WINDCODE S_INFO_LISTBOARDNAME 600129.SH 主機板 000606.SZ 主機板 002152.SZ 中小企業板 30

使用http.cookiejar生產Cookie模擬使用者登陸

# -*- coding: utf-8 -*- import re import urllib.parse import urllib.request from http.cookiejar import CookieJar #豆瓣的登入url loginurl = "https://www.douban

Mongodbaggregate限制返回

使用$project即可 db.xx.aggregate({$project:{_id:1}}) db.xx.aggregate({$match:{opTime:{$gt:1475091390000}}},{$project:{_id:1}},{$skip:5},{$lim

memcachedmemcached中flags的作用

看了一下memcached的協議,是這樣定義一個item的: Each item sent by the server looks like this: VALUE <key> <flags> <bytes> [<cas unique>]\r\n <dat

DRF框架序列化元件——驗證

單個欄位的驗證 1.在序列化器裡定義校驗欄位的鉤子方法   validate_欄位 2.獲取欄位的資料 3.驗證不通過,丟擲異常  raise serializers.ValidationError("校驗不通過的說明") 4.驗證通過,直接返回欄位資料

mysql查詢根據部分去重

mysql有個關鍵字distinct用來去重的,但是使用時只能放在查詢欄位的最前邊 如: SELECT DISTINCT user_id,age FROM t_user; 若不是放在最前邊,如: SELECT user_id, DISTINCT age FROM t_us

java list按照 物件 指定多個屬性進行排序

話不多說,上程式碼: package PjectUtils; import java.lang.reflect.Field; import java.text.NumberFormat; import java.util.Collections; import java

OPenGLopengl 64 配置

技術 添加 os x 源代碼 lar ebs 庫文件 定義 software 1.GLEW The OpenGL Extension Wrangler Library (GLEW) is a cross-platform open-source C/C++ extensio

JMeter學習(二十五)HTTP屬性管理器HTTP Cookie Manager、HTTP Request Defaults

agen 讀取 expired fault 範圍 運行時 ear 定制 只有一個 Test Plan的配置元件中有一些和HTTP屬性相關的元件:HTTP Cache Manager、HTTP Authorization Manager、HTTP Cookie Manager

Jmeter模擬發送TCP/UDP/HTTP/FTP等請求包

lose property lib 格式 自定義 ras esp tle .cn JMeter安裝UDP插件後支持發送UDP協議的請求包,官方介紹安裝插件後可以用來測試DNS, NTP, TFTP, Boot servers and many-many other syst

前端小小白的學習之路整理幾道面試題之(HTTP協議)

ase 賬號 檢測 提交數據 大型數據集 tor 添加 描述 分享 轉自:http://www.cnblogs.com/ranyonsue/p/5984001.html HTTP簡介 HTTP協議是Hyper Text Transfer Protocol(超文本傳輸

如何在win10(64系統)上安裝apache服務器

是我 是你 www blank sta install 修改 get 分享 如何在win10(64位系統)上安裝apache服務器 今天裝了Apache服務器,下面是我總結的方法: 一,準備軟件   1.64位的apache版本 傳送門:http://www.ap

Swagger2 添加HTTP head參數

nts parameter pat hand ext 一起 lai block size 大家使用swagger往往會和JWT一起使用,而一般使用jwt會將token放在head裏,這樣我們在使用swagger測試的時候並不方便,因為跨域問題它默認不能自定義head參數。然

rt-thread的圖調度算法分析

tools 檢查 span googl popu 調用函數 source != 雙層 序言 期待讀者 本文期待讀者有C語言編程基礎,後文中要分析代碼,對其中的一些C語言中的簡單語句不會介紹,但是並不要求讀者有過多的C基礎,比如指針和鏈表等不會要求太多,後面在分析代碼時,會附