freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

postMessage 相关漏洞分析与分享
2021-08-22 17:09:20

前言

postMessage API 是在 HTML5 中引入的通信方法,可以在标签中实现跨域通信。

跨域嘛,大家懂的。

要使用这个方法发送消息,只需要在目标窗口对象上进行postMessage函数的调用。也就是像这样:

targetWindow.postMessage("hello world", "*");

注意"*"的部分,postMessage的语法是.postMessage(message, targetOrigin, [transfer]),"*"表示对接收消息的窗口无限制。

相信敏感的朋友已经想到,如果一个网页存在targetWindow = window.opener,然后又调用了targetWindow.postMessage("hello world", "*");,那么,对于任何打开这个网页的网站,想接收到 "hello world" 消息,只需要有下面这样的代码:

window.addEventListener("message", function(message){
    console.log(message)
});

简而言之,如果传递的消息不是 "hello world",而是用户密码啥的,大家懂的。信息泄露,往往就是这样朴实无华,且枯燥。

不过,这只是 postMessage 相关漏洞的其中一种。存在有缺陷网页向其他网页传递消息的情况,自然也就同样存在其他网页可以向有缺陷网页传递消息的情况。

单纯从传递角度来说,想通过postMessage向任意一个网站传递信息,只需要 1. 获取对应的 window 对象;2. 发送消息。

最简单的方法,就是直接let targetWindow = window.open("任意网址", '_blank');,然后targetWindow.postMessage("hello world", "*");。

传递很简单,但只要对方网页没有相应的监听,这就不会对对方网页造成危害。

对于消息接收方来说,收到的 message 有三个属性:

data:消息正文。比如前面例子中的 "hello world"。
origin:消息发送方窗口的origin。例如http://example.com:8080。这个参数是消息发送方无法操纵篡改的。不过当然,消息发送方可以在消息发送后再导航到不同的 origin 。
source:消息发送方窗口对象的引用,便于建立双向通信。也是消息发送方无法操纵篡改的。

相信大部分朋友已经猜到了,使用 postMessage API 进行通信时,对于消息接收方,是有一个安全规范的。消息接收方应该始终使用origin或者source属性验证发送方的身份。而且,对于安全要求较高的网站,如果发送方可能是跨域的,那么在验证了origin或者source属性后,最好仍然验证接收到的消息的格式和语法,否则存在利用信任方网站安全漏洞导致攻击的可能。

window.addEventListener('message', function (e) {
    if (e.origin !== "https://www.freebuf.com") {
        return;
    }
    ……
});

现实中,大量网页没有遵守这样的安全规范,没有验证origin或者source属性的情况比比皆是。很多情况下,这样的缺陷并不会真正造成大的伤害,因为很多网页接收消息后只是把消息内容用来做判断,进行一些页面的不痛不痒的调整,这样的越权意义不大。而在另外的情况下,网页可能把消息内容放到了页面中,而这,往往能形成 XSS。

案例

对于形成 XSS,分享一个我报告过并早已修复的案例

postMessage 的 XSS 漏洞基本都需要读代码发现。首先通过工具和代码发现存在相应监听的网页(后面会讲到),然后再查看代码看使用否有可利用点。

在这个案例中,问题存在于一个可视化功能的网页,网页window.addEventListener("message"接收到消息后,首先会通过Array.isArray(event.data)验证消息内容是否是数组。然后会通过event.data.forEach(function (message)进行数组遍历。之后对message.channel进行 switch case 判断并执行对应操作。

switch case 中的一个 "OpenNotification" 引起了我的注意,因为和提示、弹出相关的功能一直都是 XSS 的重灾区,而且在到读代码这一步之前,我就已经知道对应网页所使用的 jQuery 版本存在一些和提示、弹出相关的 XSS 缺陷。

仔细查看了相应函数后,果不其然发现了 XSS 点。

到这一步就很清晰了,PoC 需要传递的消息是一个数组,数组的元素是一个对象,对象的channel属性为 "OpenNotification",message属性("OpenNotification" 中将这个属性的值传进了对应的有 XSS 点的函数)为准备好的 XSS payload。最终 PoC 的代码长这样:

test.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="postMessage() XSS test">

    <title>postMessage() XSS test</title>

</head>
<body>

<div class="wrapper-page">

    <button onclick="openWin()">OPEN</button>
    <button onclick="postXSS()">POST</button>

</div>

<script src="test.js"></script>

</body>
</html>

test.js:

window.targetWindow=null;

window.openWin=function(){
    targetWindow = window.open(<有缺陷的网页地址>, '_blank');
};

window.postXSS=function(str){
    targetWindow && targetWindow.postMessage([{"channel":"OpenNotification","message":<XSS payload>}], "*");
    console.log('post success');
};

工具

寻找这一类漏洞,需要识别出使用了相应功能的 js 文件。这一点通过 Burp 或 Fiddler 都可以轻松实现。

Burp 中,安装 J2EEScan 插件能实现对 postMessage 功能的自动被动探测,探测到使用了相应功能的 js 文件,会自动生成 issue 显示在 Target 选项卡中对应的域名下。

J2EEScan插件.png

J2EEScan插件探测显示效果.png

Fiddler 中,利用 FiddlerScript Editor,只需要在OnBeforeResponse函数中添加下面这样的代码,就可以自动将通过 Fiddler 的流量中符合条件的流量信息写入 log 文件。

oSession.utilDecodeResponse();
var oBody = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes);

var regPostMsg=/\.addEventListener\(\"message\"|\.addEventListener\(\'message\'/g;
if (regPostMsg.test(oBody)){
	var fso;
	var file;
	fso = new ActiveXObject("Scripting.FileSystemObject");
	file = fso.OpenTextFile("D:\\Fiddler\\catch\\postmsg.log",8 ,true, true); //这里设置保存的文件路径,需要事先建立好相应文件
	file.writeLine("Response url: " + oSession.url); //写入流量 URL
	file.writeLine("Response header:" + "\n" + oSession.oResponse.headers); //写入流量的响应头
	file.writeLine("\n");
	file.close();
}

值得一提的是,Fiddler 的 AutoResponder 功能做代码调试非常顺滑,是我个人非常常用的一个功能。当你在 JavaScript 代码中发现 postMessage 相关问题之后,你需要找到相应代码的触发方式。毕竟很多代码并不是在加载网页时就触发的。甚至还有些代码可能本来就是冗余,根本无法触发。

尤其对于一些复杂的功能页面,找到相应代码的触发方式,可能需要经过繁琐的调试过程。实战中面对的往往是压缩后的代码,解压后面对一大堆精简的命名,理清楚代码执行前提是一件很烧脑的事情。很多时候,把代码下载下来添加console.log,然后使用 Fiddler 的 AutoResponder 功能进行文件替换(AutoResponder 功能可以让网站加载你本地的依赖文件而不是线上的),再在页面中点击各种功能寻找触发点,是更高效的做法。

Fiddler AutoResponder 功能的教程,网上已经有很多,在此不作赘述。

除了 Burp 和 Fiddler 外,浏览器开发者工具也是可以查看监听信息的,只是说平时的实际测试过程中,Burp 和 Fiddler 的方式会更高效。以 Google 浏览器为例,Developer tools - Sources,点开 Global Listeners,里面有 message 就说明存在对 message 的监听,并且可以点开查看监听存在的具体文件。

Google浏览器Developer_tools-Sources.png

经验

最后,寻找相应漏洞的过程中,还可能会遇到另一个东西,BroadcastChannel。

如果你遇到 addEventListener 建立在 BroadcastChannel 对象上,那么,跨域的想法基本可以打消了。因为和单纯的 postMessage API 不同,BroadcastChannel 是一个同源通讯接口,实现的是同源下不同 Tab 页、frame 等之间的通讯。它本身的性质就决定了它不会有单纯 postMessage API 那样大的利用空间。

BroadcastChannel 的语法大致长这个样子:

// 连接到广播频道
let bc = new BroadcastChannel("test_channel");

// 发送消息
bc.postMessage("hello world");

// 接收消息
bc.onmessage = function (ev) { console.log(ev); }

BroadcastChannel 本身的同源限制决定了它不再需要像单纯 postMessage 那样进行验证,接收的消息直接使用就可以。

# xss # web安全 # 漏洞分析 # 越权漏洞 # 跨站脚本
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录