Web 開發中,有時我們需要實現不同頁面之間的數據傳遞和事件觸發,比如一個頁面打開了另一個頁面,然后在新的頁面中操作后需要更新原來的頁面的內容。這種場景在電商、支付、社交等領域都很常見,那么如何用js來實現不同頁面之間的交互呢?本文提供幾種常見的方法供大家學習參考!
在 Web Storage 中,每一次將一個值存儲到本地存儲時,都會觸發一個 storage 事件,通過 localStorage 結合 window.addEventListener('storage', cb) 完成 A、B 標簽頁間通信。
// A標簽頁
localStorage.setItem('send-msg', JSON.stringify({
name: 'hzd',
age: '18',
}))
// B標簽頁
window.addEventListener('storage', (data) => {
try {
console.log(data)
const msg = JSON.parse(data.newValue)
} catch (err) {
// 處理錯誤
}
})
在控制臺打印一下 data 的值,可以看到挺多信息:
BroadcastChannel 通信方式的原理就是一個命名管道,它允許讓指定的同源下瀏覽器不同的窗口來訂閱它。
每個 BroadcastChannel 對象都需要使用一個唯一的名稱來標識通道,這個名稱在同一域名下的不同頁面之間必須是唯一的,它允許同一域名下的不同頁面之間進行通信。
通過 postMessage 方法,一個頁面可以將消息發送到頻道中,而其他頁面則可以監聽 message 事件來接收這些消息。通過這種方式是短線了一種實時通信的機制,可以在不同的頁面之間傳遞信息,實現頁面間的即時交流。如下圖所示:
// A頁面
const bc = new BroadcastChannel("test_channel");
bc.postMessage("This is a test message.");
// B頁面
const bc = new BroadcastChannel("test_channel");
bc.onmessage = (event) => {
console.log(event);
};
postMessage 是 H5 引入的 API,該方法允許來自不同源的腳本采用異步方式進行有效的通信,可以實現跨文本文檔、多窗口、跨域消息傳遞,多用于窗口間數據通信,這也使它成為跨域通信的一種有效的解決方案。
下面看兩個簡單的使用例子:
示例一:
// 發送端:
<button id="btn">發送消息</button>
<script>
let device = window.open('http://localhost:63342/signal_communication/postMessage/receive.html')
document.getElementById('btn').addEventListener('click', event => {
device.postMessage('發送一條消息')
})
</script>
// 接收端:
<script>
window.addEventListener('message', event => {
console.log(event)
})
</script>
示例二:
// 發送端:
<div>
<input id="text" type="text" value="Runoob" />
<button id="sendMessage" >發送消息</button>
</div>
<iframe id="receiver" src="https://c.runoob.com/runoobtest/postMessage_receiver.html" width="300" height="360">
<p>你的瀏覽器不支持 iframe。</p>
</iframe>
<script>
window.onload = function() {
let receiver = document.getElementById('receiver').contentWindow;
let btn = document.getElementById('sendMessage');
btn.addEventListener('click', function (e) {
e.preventDefault();
let val = document.getElementById('text').value;
receiver.postMessage("Hello "+val+"!", "https://c.runoob.com");
});
}
</script>
// 接收端:
<div id="recMessage">Hello World!</div>
<script>
window.onload = function() {
let messageEle = document.getElementById('recMessage');
window.addEventListener('message', function (e) { // 監聽 message 事件
alert(e.origin);
if (e.origin !== "https://www.runoob.com") { // 驗證消息來源地址
return;
}
messageEle.innerHTML = "從"+ e.origin +"收到消息: " + e.data;
});
}
</script>
SharedWorker 是一種在 Web 瀏覽器中使用的 Web API,它允許不同的瀏覽上下文,如不同的瀏覽器標簽頁之間共享數據和執行代碼。它可以用于在多個瀏覽上下文之間建立通信通道,以便它們可以共享信息和協同工作。
與普通的 Worker 不同,SharedWorker 可以在多個瀏覽上下文中實例化,而不僅限于一個單獨的瀏覽器標簽頁或框架。這使得多個瀏覽上下文可以共享同一個后臺線程,從而更有效地共享數據和資源,而不必在每個標簽頁或框架中都創建一個獨立的工作線程。
<!-- a.html -->
<script>
let index = 0;
const worker = new SharedWorker("worker.js");
setInterval(() => {
worker.port.postMessage(`moment ${index++}`);
}, 1000);
</script>
<!-- b.html -->
<script>
const worker = new SharedWorker("worker.js");
worker.port.start();
setInterval(() => {
worker.port.postMessage("php是世界上最好的語言");
}, 1000);
worker.port.onmessage = function (e) {
if (e.data) {
console.log(e.data);
}
};
</script>
創建一個 worker.js 文件,并編寫以下代碼:
let data = "";
self.onconnect = (e) => {
const port = e.ports[0];
port.onmessage = function (e) {
if (e.data === "php是世界上最好的語言") {
port.postMessage(data);
data = "";
} else {
data = e.data;
}
};
};
最終代碼運行效果如下圖所示:
Service Worker 它是一種服務工作線程,是一種在瀏覽器背后運行的腳本,用于處理網絡請求和緩存等任務。它是一種在瀏覽器與網絡之間的中間層,允許開發者攔截和控制頁面發出的網絡請求,以及管理緩存,從而實現離線訪問、性能優化和推送通知等功能。
它在瀏覽器背后獨立運行與網頁分開,這意味著即使用戶關閉了網頁,Service Worker 仍然可以運行。可以用于實現推送通知功能。它可以注冊為推送消息的接收者,當服務器有新的通知要發送時,Service Worker 可以顯示通知給用戶,即使網頁沒有打開。
要想使用,首先我們創建兩個不同的 html 文件分別代表不同的頁面,創建一個 Service Worker 文件,并且使用 live server 開啟一個本地服務器:
<!-- a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
navigator.serviceWorker.register("worker.js").then(() => {
console.log("注冊成功");
});
setInterval(() => {
navigator.serviceWorker.controller.postMessage({
value: `moment ${new Date()}`,
});
}, 3000);
navigator.serviceWorker.onmessage = function (e) {
console.log(e.data.value);
};
</script>
</body>
</html>
<!-- b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
navigator.serviceWorker.register("worker.js").then(() => {
console.log("注冊成功");
});
setInterval(() => {
navigator.serviceWorker.controller.postMessage({
value: `moment ${new Date()}`,
});
}, 3000);
navigator.serviceWorker.onmessage = function (e) {
console.log(e.data.value);
};
</script>
</body>
</html>
創建一個 worker.js 文件并編寫以下代碼:
// worker.js
self.addEventListener("message", function (e) {
e.waitUntil(
self.clients.matchAll().then(function (clients) {
if (!clients || clients.length === 0) {
return;
}
clients.forEach(function (client) {
client.postMessage(e.data);
});
})
);
});
最終代碼運行如下圖所示:
你所編寫的 Service Worker 將遵守以下生命周期:
IndexedDB 是一種在瀏覽器中用于存儲和管理大量結構化數據的 Web API。它提供了一種持久性存儲解決方案,允許 Web 應用程序在客戶端存儲數據,以便在不同會話、頁面加載或瀏覽器關閉之間保留數據。
與傳統的 cookie 或 localStorage 等存儲方式不同,IndexedDB 更適合存儲復雜的、結構化的數據,例如對象、數組、鍵值對等。這使得它特別適用于應用程序需要存儲大量數據、執行高級查詢或支持離線工作的情況。
要實現跨標簽通信,如下代碼所示:
<!-- a.html -->
<script>
let index = 0;
// 打開或創建 IndexedDB 數據庫
const request = indexedDB.open("database", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const objectStore = db.createObjectStore("dataStore", {
keyPath: "key",
});
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["dataStore"], "readwrite");
const objectStore = transaction.objectStore("dataStore");
// 存儲數據
objectStore.put({ key: "supper", value: `moment` });
transaction.oncomplete = () => {
db.close();
};
};
</script>
<!-- b.html -->
<script>
// 打開相同的 IndexedDB 數據庫
const request = indexedDB.open("database", 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["dataStore"], "readonly");
const objectStore = transaction.objectStore("dataStore");
// 獲取數據
const getRequest = objectStore.get("supper");
getRequest.onsuccess = (event) => {
const data = event.target.result;
if (data) {
console.log(data.value);
}
};
transaction.oncomplete = () => {
db.close();
};
};
</script>
最終代碼運行如下圖所示:
<!-- a.html -->
<script>
let index = 0;
setInterval(() => {
document.cookie = `supper=moment ${index++}`;
}, 1000);
</script>
<!-- b.html -->
<script>
console.log("cookie 的值為: ", document.cookie);
setInterval(() => {
console.log("cookie 的值發生了變化: ", document.cookie);
}, 1000);
</script>
具體代碼運行效果如下圖所示:
作者:前端掘金者H
鏈接:https://juejin.cn/post/7268602250653319202
什么是跨域?
瀏覽器發送的請求地址(URL)與所在頁面的地址 不同(端口/協議/域名 其一不同)。簡言之,瀏覽器發出的請求url,與其所在頁面的url不一樣。此時,同源策略會讓瀏覽器拒收 服務器響應回來的數據,報錯信息如下:
同源策略
同源機制是瀏覽器處于安全考慮,只允許相同源的接口進行相互通信。不同源的接口在沒有得到對方授權的情況下不能夠讀寫對方的資源。這個很好理解,如果沒有同源機制,瀏覽器中的cookie就可以被隨意讀取,一旦用戶進入惡意網站,惡意網站就可以讀取用戶的cookie,偽造用戶進行登陸轉款等操作。
同源策略的交互方式有三種:
同源指的的是同時滿足以下三個條件的地址:
如:
http://www.baidu.com https://www.baidu.com //這兩個網址由于協議不同,因此不屬于同源 http://www.baidu.top http://www.baidu.com //這兩個網址由于域名不同,因此不屬于同源 http://www.baidu.com:80 http://www.baidu.com:8080 //這兩個網址由于端口不同,因此不屬于同源 https://www.baidu.com/login https://www.baidu.com/add //這兩個網址屬于同源
1.cors
cors跨域資源共享允許是在服務端"Access-Control-Allow-Origin"字段設置的,當將cors設置為允許某個地址訪問時,該地址就可以跨域訪問這個服務器地址。當cors設置為"*"時即允許所有地址訪問時,則表示所有地址都可以跨域訪問這個服務器地址的資源。
cors具體設置根據使用的后端語言不同會有所區別,此處以node的express框架設置為例:
客戶端設置:
<script> $.ajax({ type: 'post', url: 'http://127.0.0.1:5000', success: function(res) { console.log('成功跨域'); console.log(res); } }) </script>
node端設置:
res.header("Access-Control-Allow-Origin", "*");//允許所有地址跨域訪問服務器資源
2.jsonp
我們在開始講同源策略的時候提到過,同源策略是允許script標簽跨域訪問資源的。jsonp方法就是利用的這個原理跨域的。
以Jquery的ajax請求為例:
<script> function jsonpCallback(data){ console.log("成功跨域,數據為:",data) ; } $.ajax({ method:"GET", url:"https://www.baidu.com", dataType:"jsonp", jsonpCallback:"jsonpCallback" //設置jsonp的回調函數 }) <script>
優缺點:
3.window.postMessage
postMessage跨域方法是HTML5的新特性,該方法必須接受兩個參數:
示例:
發送端代碼:http://localhost:8081/send.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" onclick="post()" value="send"> <script> var my_window=window.open("http://localhost:8082/index.html"); function post() { var data={ name:"martin", age:18 }; my_window.postMessage(JSON.stringify(data),"http://localhost:8082/index.html"); } </script> </body> </html>
點擊send按鈕,send端跨域postMessage發出一個字符串
接收端代碼:http://localhost:8082/reciever.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="text"> <script> window.addEventListener("message",function (message) { if(message.origin=="http://localhost:8081"){ //檢驗信息的來源,只要想要地址發送的信息 var input=document.getElementsByTagName("input")[0]; input.value=message.data; //將接收到的信息填入input框 console.log(message); } }) </script> </body> </html>
此時觀察接受地址的input框,發現已經接受到了數據,至此跨域成功。
備注:想要將幾個網頁在不同的端口打開,可以安裝http-server這個node模塊,無需配置,即裝即用。
4.proxy(代理)
原理:因為同源策略只是針對瀏覽器的安全策略,但是服務端并不受同源策略的限制,也就不存在跨域的問題。
示例:
當前狀況:
需求:
代理跨域:
總結:
從這個例子的描述中可以看出,代理跨域方案說白了就是讓代理作為一個中間人,在前端和目標地址之間擔任一個信息轉發的功能,就有點像婆媳有矛盾不能直接溝通,所有一般讓丈夫做為中間人來傳話。
:本文為“小米安全中心”原創 ,轉載請聯系“小米安全中心”
背景
價值: 00
漏洞原因:postMessage跨域漏洞導致,利用websocket接收用戶認證token
原文地址:https://labs.detectify.com/2017/02/28/hacking-slack-using-postmessage-and-websocket-reconnect-to-steal-your-precious-token/
基礎知識
在了解這個漏洞的時候我們需要了解以下基礎的知識:PostMessage
PostMessage
PostMessage API是一個很簡單的實現跨域的方法,最基本的使用方法是:
B站的data數據,發送數據端
獲取數據代碼:
PostMessage產生的漏洞
偽造獲取數據端
若是發送數據端的第二個參數使用的是"*" 那么表示的是這個數據可以被任何域獲取到,這時候只要構造window.addEventListener來接受數據就可以了
偽造發送數據端
來看一段代碼:
可以看到對接收的message.origin進行了判斷,但是使用的正則并不標準(小米安全中心入駐安全脈搏賬號發布 轉載請注明來源安全脈搏https://www.secpulse.com/archives/56637.html)
直接利用“wwwaexamplesender.com“, “wwwbexamplesender.com” etc.進行繞過,用postMessage發送數據給他接收
使用的poc可以參考:
測試代碼:window.postMessage("hello", "*")
利用xss進行攻擊
先看代碼:
這個對來源進行了嚴格的限制,可以通過查找http://www.examplesender.com/上面的xss進行攻擊
查看是否使用PostMessage
這個可以使用chrome下的開發者工具,在source ——> Global Listeners 下面可以看到
案例
上面slack的漏洞是一個案例,總體的思路就是站點監聽著一個message,并且沒有判斷message的來源,導致可以給他發message,message中有websocket的url的話,站點會和發送message的站點建立websocket鏈接,兵器會把認證后的token傳遞給發送者站點
該作者還有個PostMessage的漏洞,會影響到百萬網站,是AddThis這個插件導致的,不難理解。
1.qq郵箱
再國內的站點了翻了一個下午也沒有能找到一個可以利用的漏洞,不過在qq郵箱還是可以找到可以練習的地方
首先打開mail.qq.com 使用開發者工具打開查看listeners
我們構造測試代碼:
window.postMessage("hello", "*")
這時候對代下斷點進行調試,可以最終得到:
window.postMessage('{"action":"resize","width":"222","height":400}', "*")
方法有兩個一個是close,一個是resize。
直接執行的話就可以看到登入框的大小屬性產生了變化,構造poc:
構造html頁面,訪問這個頁面,就能看到登入框的大小產生了變化,
這個只是個案例,要是郵箱中接收的信息做了其他處理,那么就有可能產生其他的問題
2.微博
qq郵箱是偽造發送者,那微博這個就是偽造接收者。同樣打開微博查看信息
可以看到一行代碼:
b
&& b
.contentWindow && b
.contentWindow.postMessage ? b
.contentWindow.postMessage(g, "*")
這個很明顯的使用了*的配置
構造poc:
訪問html頁面觸發:
這個里面沒有任何可以利用的東西,但是一旦有用戶的敏感信息,那么就可以跨域獲取這些信息
參考資料
http://www.cnblogs.com/dolphinX/p/3464056.html
https://labs.detectify.com/2016/12/15/postmessage-xss-on-a-million-sites/
【注:本文為“小米安全中心”原創 小米安全中心入駐安全脈搏賬號發布 轉載請注明來源安全脈搏】
【安識科技,是一家專注于賬號安全、企業風險評估的技術型企業。旗下擁有基于云+端的自研產品多因素令牌、基于插件的主被動多種掃描的企業級漏洞檢測云平臺。】
【安全脈搏:分享技術、悅享品質。文章僅代表作者看法,如有不同觀點,歡迎添加安全脈搏微信號:SecPulse,進行交流。】
*請認真填寫需求信息,我們會在24小時內與您取得聯系。