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小时内与您取得联系。