安全 初探同源策略及其安全

今天瞭解了一些有關瀏覽器同源策略以及CSRF***之後,覺得挺有意思的,所以特此總結一波,給自己囤一點乾貨

這篇文章主要包含三個大的方面:
1.同源策略是什麼及其作用
2.如何繞過同源策略
3.CSEF***及防禦

878

同源策略及其安全.png

1.同源策略(same-origin policy):

1.1 什麼叫做同源:

同源是針對域名來說的,對於任意的域名,只要他們的協議(HTTP,HTTPS)一致,主機一致,端口號一致,就可以將其認爲是同源的。

1.2 那麼同源策略的作用是什麼?

** 說到作用,那麼就不可避免地要提及一下爲什麼會有這個策略的產生吧**

引用一段阮一峯大神的描述:

同源政策的目的,是爲了保證用戶信息的安全,防止惡意的網站竊取數據。
設想這樣一種情況:A網站是一家銀行,用戶登錄以後,又去瀏覽其他網站。如果其他網站可以讀取A網站的 Cookie,會發生什麼?
很顯然,如果 Cookie 包含隱私(比如存款總額),這些信息就會泄漏。更可怕的是,Cookie 往往用來保存用戶的登錄狀態,如果用戶沒有退出登錄,其他網站就可以冒充用戶,爲所欲爲。因爲瀏覽器同時還規定,提交表單不受同源政策的限制。
由此可見,"同源政策"是必需的,否則 Cookie 可以共享,互聯網就毫無安全可言了。

再來一段Wikipedia上的描述:

In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number.This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page's Document Object Model.
This mechanism bears a particular significance for modern web applications that extensively depend on HTTP cookies to maintain authenticated user sessions, as servers act based on the HTTP cookie information to reveal sensitive information or take state-changing actions. A strict separation between content provided by unrelated sites must be maintained on the client-side to prevent the loss of data confidentiality or integrity.

看了這麼多的描述之後,我也來一個自己總結的吧。

我認爲同源策略之所以出現,就是爲了提高在瀏覽器上網的安全性。這裏的 安全性主要是針對 cookie來說的。因爲 cookie 保存着我們每個人的上網信息,甚至包含一些涉及個人重要隱私的信息,而沒有同源策略之前,我們的這些信息,也即 cookie很容易被他人利用,去做一些羞羞的事情,所以爲了保證我們每個網民能夠安全的上網,這個策略就應用而生啦。(在這裏,我要對NetScape說一聲感謝)

** 那麼,迴歸正題,這個同源策略的具體作用都有哪些呢? **

一旦我們進行非同源的跨站訪問,那麼爲了安全起見,在這個過程中,有一些數據的傳輸會受到限制。比如:

  • Cookie、LocalStorage 和 IndexDB 無法讀取。

  • DOM無法獲得

  • AJAX請求不能發送

這樣一來,雖然看起來我們的安全性被牢牢地掌握在自己手中,但是自己的手腳也多多少少受到了一些限制,什麼生意呢?如果在我們自己搞的東西中,真的需要進行跨域訪問呢?難道當初NetScape在設計的時候就沒考慮到這一點嗎?

放心,其實是有辦法的。

2.那麼都有哪些辦法能夠繞過同源策略來實現相應的功能呢?

  1. 對於Cookie的傳輸:
    1.1 如果我們現在有兩個網頁,它們一級域名一致,只是二級域名不同,那麼如果要做到使這兩個域名能夠實現cookie的共享,只需要將這兩個頁面的 document.domain設置爲一級域名即可。
    比如 頁面一 http://xiao.haha.com/a.html 頁面二 http://qian.haha.com/b.html 那麼只需要將 document.domain = haha.com即可
    1.2 對於兩個毫無關係的頁面,如果要進行cookie的傳輸,那麼需要藉助標籤  <img>  <script>  <iframe> 即可(這裏就是CSRF***的起源之地)

  2. 對於DOM的傳輸:
    2.1 片段識別符(fragment identifier)
    2.2 window.name
    2.3 跨文檔通信API(Cross-document messaging)

3.對於AJAX請求:
3.1 利用JSONP通信標準
其基本思想是既然AJAX已經被廢掉了,那麼就換一種還可以繼續請求的辦法。於是就想到了可以利用 <script> 標籤來向跨源地址發起請求。
示例代碼

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}
window.onload = function () {
 addScriptTag('http://example.com/ip?callback=foo');
}function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

上面代碼通過動態添加 <script> 元素,向服務器 example.com 發出請求。注意,該請求的查詢字符串有一個 callback 參數,用來指定回調函數的名字,這對於JSONP是必需的。
服務器收到這個請求以後,會將數據放在回調函數的參數位置返回。

foo({  "ip": "8.8.8.8"});

由於 <script> 元素請求的腳本,直接作爲代碼運行。這時,只要瀏覽器定義了foo函數,該函數就會立即調用。作爲參數的JSON數據被視爲JavaScript對象,而不是字符串,因此避免了使用 JSON.parse 的步驟。

3.2 利用WebSocket協議
WebSocket是一種通信協議,使用ws://(非加密)和wss://(加密)作爲協議前綴。該協議不實行同源政策,只要服務器支持,就可以通過它進行跨源通信。

示例如下:

GET /chat HTTP/1.1Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://example.com/

在上邊的示例中,最後一個屬性 origin 是重點。該字段表示請求的請求源,然後瀏覽器根據該地址,判斷是否處在白名單中,如果處在,則可以進行本次通信,反之不可。

3.3 利用CORS(跨源資源分享)標準
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時滿足以下兩大條件,就屬於簡單請求。

  • 請求方法是以下三種方法之一:
    1.HEAD
    2.GET
    3.POST

  • HTTP的頭信息不超出以下幾種字段:
    1.Accept
    2.Accept-Language
    3.Content-Language
    4.Last-Event-ID
    5.Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

** 對於簡單請求: **
瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin字段。
如果Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因爲HTTP迴應的狀態碼有可能是200。

** 對於非簡單請求: **
非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱爲"預檢"請求(preflight)。
瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,否則就報錯。

** 注: CORS協議支持所有的HTTP請求,而JSONP只支持GET請求,所以在這一點上,CORS協議要強大的多。 **

3.CSRF***又是啥子武器呢?

3.1 什麼是CSRF***?

CSRF(Cross-site request forgery),中文名稱:跨站請求僞造,也被稱爲:one click attack/session riding,縮寫爲:CSRF/XSRF。

3.2 CSRF***的原理?

利用cookie,盜用身份從而進行邪惡活動。

具體的作案手段都有哪些呢?

示例1:
銀行網站A,它以GET請求來完成銀行轉賬的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危險網站B,它裏面有一段HTML的代碼如下,便會成功。

<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

** 由於同源策略對img標籤沒有限制,所以向在img標籤中的src地址發起請求時,會直接將用戶的cookie傳遞過去,從而盜取用戶身份進行非法操作。 **

示例2:
爲了杜絕上面的問題,銀行決定改用POST請求完成轉賬操作。
銀行網站A的WEB表單如下:

  <form action="Transfer.php" method="POST">    <p>ToBankId: <input type="text" name="toBankId" /></p>    <p>Money: <input type="text" name="money" /></p>    <p><input type="submit" value="Transfer" /></p>  </form>

後臺處理頁面Transfer.php如下:

<?php    session_start();
    if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money']))
    {
        buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);
    }
  ?>

危險網站B,仍然只是包含那句HTML代碼,同樣也會成功。

<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

** 爲什麼這次還可以呢,這個鍋就得PHP背了,PHP在後端用的是$_REQUEST來獲取參數,那麼它既可獲取GET參數,也可以得到POST。所以在這裏,依然用的是GET請求方式了, 同樣地在JAVA中, request對象也是如此,不能區分GET和POST**

示例3:
經過前面2個慘痛的教訓,銀行決定把獲取請求數據的方法也改了,改用$_POST,只獲取POST請求的數據,後臺處理頁面Transfer.php代碼如下:

  <?php    session_start();
    if (isset($_POST['toBankId'] && isset($_POST['money']))
    {
        buy_stocks($_POST['toBankId'], $_POST['money']);
    }
  ?>

然而,危險網站B與時俱進,它改了一下代碼,照樣得手:

<html>
  <head>    <script type="text/javascript">      function steal()
      {
               iframe = document.frames["steal"];
               iframe.document.Submit("transfer");
      }
    </script>  </head>  <body onload="steal()">
    <iframe name="steal" display="none">      <form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php">        <input type="hidden" name="toBankId" value="11">        <input type="hidden" name="money" value="1000">      </form>    </iframe>  </body></html>

其實在網站B的代碼中,就是通過利用同源策略對iframe標籤的不限制,從而在iframe標籤中又重新構造了一個post方式提交的表單,同樣奏效了。

3.3 那麼如何防禦呢?

  1. 在服務器端進行防禦 :Cookie Hashing(所有表單都包含同一個僞隨機值):
    在表單裏增加Hash值,以認證這確實是用戶發送的請求。

 <?php    $hash = md5($_COOKIE['cookie']);
  ?>  <form method=”POST” action=”transfer.php”>
    <input type=”text” name=”toBankId”>
    <input type=”text” name=”money”>
    <input type=”hidden” name=”hash” value=”<?=$hash;?>”>
    <input type=”submit” name=”submit” value=”Submit”>
  </form>

然後在服務器端進行Hash值驗證

   <?php        if(isset($_POST['check'])) {
             $hash = md5($_COOKIE['cookie']);
             if($_POST['check'] == $hash) {
                  doJob();
             } else {
        //...
             }
        } else {
      //...        }      ?>

利用此種hash方法,即使他人拿到cookie,也無法再次獲取到原來的那個值,也就不能再盜取身份了。所以這種防禦幾乎可以杜絕很大一部分的***了,但是還有一小部分的不能防禦到,是因爲一旦遇到XSS***,讓他人拿到原始的cookie數據,那同樣GG。

  1. 每次提交表單都輸入驗證碼。此種方法太過繁瑣,不符合實際使用情況。

  2. One-Time Tokens(不同的表單包含一個不同的僞隨機值)

4.小結:

這篇文章是對同源策略及其安全性方面進行的瞭解和小結。其中還有一些細節性的問題沒有理解到位,這次總結的目的並不會糾結於細節之處,而是從一個整體的角度來了解、學習一下這方面的東西,讓自己有一個大致的印象即可。



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