【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攻擊。所以,在應用跨域的場景,需要格外注意安全問題。