postMessage 相关漏洞分析与分享

前言

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 那样进行验证,接收的消息直接使用就可以。

文章首发于 FreeBuf.COM

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273

推荐阅读更多精彩内容

  • 一、 原型和原型链 所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。 引用类型有一个_ _ ...
    BigTooth_3611阅读 210评论 0 0
  • HTTP 状态码知道哪些? 200(ok):请求成功; 301(Moved Permanently):请求的资源已...
    darkTi阅读 272评论 0 0
  • 对称加密与非对称加密 对称密钥加密(Symmetric-Key Encryption),是指加密和解密使用同一个密...
    脆皮鸡大虾阅读 303评论 0 0
  • 写在前面 文章内容参考自https://twosecurity.io/http://www.4hou.com/we...
    Ackerzy阅读 5,205评论 0 9
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 5,986评论 0 4