什么是 postMessage
postMessage 是一种跨源通信的方法不受“同源策略”限制,允许不同源的网页之间进行安全通信,常用的跨域通信方法还包括 CORS、JSONP。它广泛应用于许多现代Web应用中,比如嵌入的iframe和跨窗口的消息传递。
其中同源策略是重要的安全策略,同源指的是协议、域名、端口都相同。非同源的两个资源间不能通信。
postMessage跨源通信不受“同源策略”限制的前提需要发送方需要有接收方window的引用。同时发送方调用接收方window的postMessage方法发送消息,接收方的消息监听器收到消息进行后续处理。
postMessage导致的安全问题及修复
未指定接收方origin
潜在攻击场景造成上图存在安全风险原因主要是发送方(内部 iframe)直接向 top 发送消息,而不是其直接父级。使用了通配符 "*" 作为 targetOrigin,允许任何源接收消息。消息内容包含敏感信息(URL、调用栈、cookie 等)。
在复杂的嵌套 iframe 结构中建议明确指定目标源,只向直接父窗口发送消息而不是 直接向top发送消息,最小化通过 postMessage 传输的敏感信息如上面图中的cookie等。
// 发送消息 window.postMessage("hello", "*"); // 接收消息 window.addEventListener("message", function(event) { console.log("Message received: ", event.data); });
当你使用 postMessage 将数据发送到其他窗口时,始终指定精确的目标 origin,而不是 *,就比如上面的示例代码和图中的错误示范会导致恶意网站可以在你不知情的情况下更改窗口的位置,因此它可以拦截使用 postMessage 发送的数据。
消息传输中的postMessage数据{"data":"hello", "origin": "https://test.com/", "source": a},其中包括data: postMessage的第一个参数发送信息,origin: 发送方的源(协议、域名、端口),source: 发送方window的引用为可选内容。
没有验证消息的来源
parseMessage: function(e) { e = "append::<style onload=\"eval(self.name)\">:" if ("string" == typeof e) { var r = e.split("::"), t = r[2], ["append", "<style onload=\"eval(self.name)\">"] , n = r[0], n = "append" , i = r[1], "<style onload=\"eval(self.name)\">" try { n[n](i) } catch (a) {} } }, ... append: function(e) { try { e = decodeURIComponent(e), $("body").append(e) } catch (t) {} },
提供 append 接口给页面写入内容,但没有验证消息来源只要收到消息就进行处理。从任意域 iframe调用 frames[0].postMessage("append::aaa", "*"),就可以往控制台页面写入aaa,写入XSS代码就可以执行任意恶意JS。
假设有一个页面使用类似这样的代码来处理 postMessage:
window.addEventListener('message', function(event) { parseMessage(event.data); });
这里没有对 event.origin 进行任何检查,直接将接收到的数据传给 parseMessage 函数。
攻击者从任意域iframe发送如下信息就会导致parseMessage 函数解析这个消息,append 函数会将解码后的内容添加到body中最终导致执行恶意 JavaScript 代码。
targetWindow.postMessage('append::<style onload="eval(alert(\'XSS via postMessage\'))">', '*');
修复方式:
始终验证 postMessage 的来源,对接收到的数据进行严格的验证和清理,避免直接执行或插入接收到的数据
window.addEventListener('message', function(event) { if (event.origin !== "https://trusted-domain.com") return; });
验证origin的方法可绕过
1.正则表达式缺少起止符、点号没转义:
let re = /imgcache.test.com|user.openqa.test.com|www.dnspod.cn/ if(re.test(e.origin)){ }
问题分析:
缺少起止符(^ 和 $):允许部分匹配
点号 (.) 没有转义:在正则表达式中,点号匹配任意字符
绕过方式:
arbitrary.imgcache.test.com.subdomain.example.com 利用了缺少起止符,只要包含匹配字符串即可 imgcacheatest.com 利用了点号没转义,"a" 可以匹配任意字符 imgcacheatestacom.example.com 结合了上述两个问题
修复方式:
let re = /^(imgcache\.test\.com|user\.openqa\.test\.com|www\.dnspod\.cn)$/ if(re.test(e.origin)){ }
2.endsWith 匹配子域名没加点:
if(e.origin.endsWith('test.com')){ }
问题分析:
未考虑到域名边界,任何以 'test.com' 结尾的字符串都会被匹配
绕过方式:
arbitrarytest.com 添加任意前缀的域名虽然不是test.com的子域名但仍然通过了检查
修复方式:
在使用正则表达式验证时,务必使用准确的模式匹配,包括起止符和正确的转义。
使用 endsWith 等方法时,需要考虑到域名的结构,确保只匹配预期的域名。
最安全的方法是使用精确匹配,例如维护一个允许的源列表,并进行完全相等的比较。
if(e.origin.endsWith('.test.com') || e.origin === 'test.com'){ }
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)