1. 程式人生 > >【XSS技巧拓展】————3、跨域方法總結

【XSS技巧拓展】————3、跨域方法總結

最近面試問的挺多的一個問題,就是JavaScript的跨域問題。在這裡,對跨域的一些方法做個總結。由於瀏覽器的同源策略,不同域名、不同埠、不同協議都會構成跨域;但在實際的業務中,很多場景需要進行跨域傳遞資訊,這樣就催生出多種跨域方法。

具備src的標籤

  • 原理:所有具有src屬性的HTML標籤都是可以跨域的

在瀏覽器中,<script><img><iframe><link>這幾個標籤是可以載入跨域(非同源)的資源的,並且載入的方式其實相當於一次普通的GET請求,唯一不同的是,為了安全起見,瀏覽器不允許這種方式下對載入到的資源的讀寫操作,而只能使用標籤本身應當具備的能力(比如指令碼執行、樣式應用等等)。

JSONP跨域

  • 原理:<script>是可以跨域的,而且在跨域指令碼中可以直接回調當前指令碼的函式

script標籤是可以載入異域的JavaScript並執行的,通過預先設定好的callback函式來實現和母頁面的互動。它有一個大名,叫做JSONP跨域,JSONP是JSON with Padding的略稱。它是一個非官方的協議,明明是載入script,為啥和JSON扯上關係呢?原來就是這個callback函式,對它的使用有一個典型的方式,就是通過JSON來傳參,即將JSON資料填充進回撥函式,這就是JSONP的JSON+Padding的含義。JSONP只支援GET請求。

前端程式碼:

<script type="text/javascript">
    function dosomething(jsondata){
        //處理獲得的json資料
    }
</script>
<script src="http://haorooms.com/data.php?callback=dosomething"></script>

後臺程式碼:

<?php
$callback = $_GET['callback'];//得到回撥函式名
$data = array('a','b','c');//要返回的資料
echo $callback.'('.json_encode($data).')';//輸出
?>

跨域資源共享(CORS)

  • 原理:伺服器設定Access-Control-Allow-Origin HTTP響應頭之後,瀏覽器將會允許跨域請求

CORS是HTML5標準提出的跨域資源共享(Cross Origin Resource Share),支援GET、POST等所有HTTP請求。CORS需要伺服器端設定Access-Control-Allow-Origin頭,否則瀏覽器會因為安全策略攔截返回的資訊。

Access-Control-Allow-Origin: *              # 允許所有域名訪問,或者
Access-Control-Allow-Origin: http://a.com   # 只允許所有域名訪問

CORS又分為簡單跨域和非簡單跨域請求,有關CORS的詳細介紹請看阮一峰的跨域資源共享 CORS 詳解,裡面講解的非常詳細。

document.domain

  • 原理:相同主域名不同子域名下的頁面,可以設定document.domain讓它們同域

我們只需要在跨域的兩個頁面中設定document.domain就可以了。修改document.domain的方法只適用於不同子域的框架間的互動,要載入iframe頁面。

例如:
1.、在頁面 http://a.example.com/a.html 設定document.domain

<iframe id = "iframe" src="http://b.example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'example.com';//設定成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子視窗的 window 物件
    }
</script>

2、在頁面http:// b.example.com/b.html 中設定document.domain

<script type="text/javascript">
    document.domain = 'example.com';//在iframe載入這個頁面也設定document.domain,使之與主頁面的document.domain相同
</script>

window.name

  • 原理:window物件有個name屬性,該屬性有個特徵:即在一個視窗(window)的生命週期內,視窗載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的許可權,window.name是持久存在一個視窗載入過的所有頁面中的。

這裡有三個頁面:

  • sever.com/a.html 資料存放頁面
  • agent.com/b.html 資料獲取頁面
  • agent.com/c.html 空頁面,做代理使用

a.html中,設定window.name作為需要傳遞的值
b.html中,當iframe載入後將iframe的src指向同域的c.html,這樣就可以利用iframe.contentWindow.name獲取要傳遞的值了

<body>
  <script type="text/javascript">
  iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  var state = 0;
  iframe.onload = function() {
    if(state === 1) {
      var data = JSON.parse(iframe.contentWindow.name);
      alert(data);
      iframe.contentWindow.document.write('');
      iframe.contentWindow.close();
      document.body.removeChild(iframe);
    } else if(state === 0) {
      state = 1;
      iframe.contentWindow.location = 'http://agent.com/c.html';
    }
  };
  iframe.src = 'http://sever.com/a.html';
  document.body.appendChild(iframe);
  </script>
</body>

成功獲取跨域資料,效果如下:

window.postMesage

  • 原理: HTML5新增的postMessage方法,通過postMessage來傳遞資訊,對方可以通過監聽message事件來監聽資訊。可跨主域名及雙向跨域。

這裡有兩個頁面:

agent.com/index.html 
server.com/remote.html

原生代碼index.html

<body>  
    <iframe id="proxy" src="http://server.com/remote.html" onload = "postMsg()" style="display: none" ></iframe>  
    <script type="text/javascript">  
        var obj = {  
            msg: 'hello world'  
        }  
        function postMsg (){  
            var iframe = document.getElementById('proxy');  
            var win = iframe.contentWindow;  
            win.postMessage(obj,'http://server.com');  
        }  
    </script>  
</body>

postMessage的使用方法: otherWindow.postMessage(message, targetOrigin);

  • otherWindow: 指目標視窗,也就是給哪個window發訊息,是
  • window.frames 屬性的成員或者由 window.open 方法建立的視窗
  • message: 是要傳送的訊息,型別為 String、Object (IE8、9 不支援)
  • targetOrigin: 是限定訊息接收範圍,不限制請使用 ‘*’

server.com上remote.html,監聽message事件,並檢查來源是否是要通訊的域。

<head>
    <title></title>
    <script type="text/javascript">
        window.onmessage = function(e){
            if(e.origin !== 'http://localhost:8088') return;
            alert(e.data.msg+" from "+e.origin);
        }
    </script>
</head>

location.hash

原理:

  • 這個辦法比較繞,但是可以解決完全跨域情況下的腳步置換問題。原理是利用location.hash來進行傳值。www.a.com下的a.html想和www.b.com下的b.html通訊(在a.html中動態建立一個b.html的iframe來發送請求)
  • 但是由於“同源策略”的限制他們無法進行交流(b.html無法返回資料),於是就找個中間人:www.a.com下的c.html(注意是www.a.com下的)。
  • b.html將資料傳給c.html(b.html中建立c.html的iframe),由於c.html和a.html同源,於是可通過c.html將返回的資料傳回給a.html,從而達到跨域的效果。

a.html程式碼如下:

<script>
function startRequest(){  
    var ifr = document.createElement('iframe');  
    ifr.style.display = 'none';  
    ifr.src = 'http://www.b.com/b.html#sayHi'; //傳遞的location.hash 
    document.body.appendChild(ifr);  
}  
function checkHash() {  
    try {  
        var data = location.hash ? location.hash.substring(1) : '';  
        if (console.log) {  
            console.log('Now the data is '+data);  
        }  
    } catch(e) {};  
}  
setInterval(checkHash, 2000); 
window.onload = startRequest;
</script>

b.html程式碼如下:

<script>
function checkHash(){
  var data = '';
  //模擬一個簡單的引數處理操作
  switch(location.hash){
    case '#sayHello': data = 'HelloWorld';break;
    case '#sayHi': data = 'HiWorld';break;
    default: break;
  }
  data && callBack('#'+data);
}
function callBack(hash){
  // ie、chrome的安全機制無法修改parent.location.hash,所以要利用一箇中間的www.a.com域下的代理iframe
  var proxy = document.createElement('iframe');
  proxy.style.display = 'none';
  proxy.src = 'http://localhost:8088/proxy.html'+hash;  // 注意該檔案在"www.a.com"域下
  document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>

由於兩個頁面不在同一個域下,IE、Chrome不允許修改parent.location.hash的值,所以要藉助於a.com域名下的一個代理iframe,這裡有一個a.com下的代理檔案c.html。Firefox可以修改。 
c.html程式碼如下:

<script>parent.parent.location.hash = self.location.hash.substring(1);  </script>

直接訪問a.html,a.html向b.html傳送的訊息為”sayHi”;b.html通過訊息判斷返回了”HiWorld”,並通過c.html改變了location.hash的值

flash URLLoader

flash有自己的一套安全策略,伺服器可以通過crossdomain.xml檔案來宣告能被哪些域的SWF檔案訪問,SWF也可以通過API來確定自身能被哪些域的SWF載入。當跨域訪問資源時,例如從域baidu.com請求域google.com上的資料,我們可以藉助flash來發送HTTP請求。首先,修改域google.com上的crossdomain.xml(一般存放在根目錄,如果沒有需要手動建立) ,把baidu.com加入到白名單。其次,通過Flash URLLoader傳送HTTP請求,最後,通過Flash API把響應結果傳遞給JavaScript。Flash URLLoader是一種很普遍的跨域解決方案,不過需要支援iOS的話,這個方案就不可行了。

小結

總的來說,常見的跨域方法如上述。在不同的業務場景下,各有適合的跨域方式。跨域解決了一些資源共享、資訊互動的難題,但是有的跨域方式可能會帶來安全問題,如jsonp可導致水坑攻擊,<img>等標籤會被用來進行xss或csrf攻擊。所以,在應用跨域的場景,需要格外注意安全問題。