whip1ash

postMessage安全性的一点点研究

2018-06-14

说点废话

近期深刻认识到自己能力不足,水平有限,必须沉下心来进行学习和研究,否则一辈子除了搬砖什么都做不了。
postMessage是为了解决跨窗口跨域的情况,使用不当会存在一些安全问题(虽然我没挖到Orz)。

postMessage是什么

Syntax

1
targetWindow.postMessage(message, targetOrigin, [transfer]);

不造轮子了,mdn上说的比较清楚了。

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

当然了,发送消息是不行的,目标窗口肯定需要接受这个消息,所以需要在targetWindow添加一个Message的监听器。

1
2
3
4
window.addEventListener('message',function(event){
//event.data就是接收到的数据
console.log(event.data);
})

这个MessageEvent除了data属性还有source属性和origin属性是比较常见的。source属性就是发送消息的窗口的window对象,origin属性顾名思义就是发送消息的源。

在我实际测试后,无法向使用window.open得到的window对象发送消息,就像这样的。

1
2
var popup = window.open('http://www.target.com/listener.html');
popup.postMessage('Hello World!','http://www.target.com');

神奇的是,在使用setInterval大法后就正常了,但是仍然收不到第一个请求。推测是originWindow发送message时,targetWindow监听器没有加载完成。从网上随便找了个sleep函数,就像下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var popup = window.open("http://www.poc.com/listener.html");
function post(){
popup.postMessage('message','*');
}

function sleep(numberMillis) {
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return;
}
}
sleep(10000);
post();

但是sleep函数执行时,targetWindow也给sleep了。
神奇的js。

更正: 使用setTimeout可以完美解决这个问题。

postMessage有什么问题

伪造接收端

伪造接收端就比较简单了,如果你见到了下面这样的写法

1
targetWindow.postMessage(message,'*')

就意味着任意域可以接收到这条消息。现实往往不像看起来那么美好,主要问题不是人家让不让你收(origin),而是发不发给你(targetWindow)。也就是说如果人家的targetWindow是子窗口,那么你是根本收不到消息哒~
发送给父窗口的情况有可能存在于一些js的widget,或者iframe的页面中。如果是发送给window.parent.prames,那么很遗憾的告诉你,基本也是没希望的。因为大部分网站已经设置X-Frame-Options: Origin,只有同源才可以使用iframe包裹。

伪造发送端

如果监听器(EventListener)没有校验消息的来源(origin),或者校验存在问题的时候,就意味着我们可以给它发送消息。并且会代入程序逻辑。
比如下面这样的

1
2
3
4
5
6
7
8
window.addEventListener('message',function(event){
var data = event.data;
//肯定不会有人直接这么写,在这里简化过程。
eval(data);
//xss
var scriptEle = document.createElement('script');
scriptEle.src = data;
})

正确的写法应该是这样的

1
2
3
4
5
6
window.addEventListener('message',function(event){
if(event.orgin !== 'http://www.origin.com'){
return 0;
}
// process data
})

如何去挖

如何伪造接收端上面已经说得比较清楚了,使用chrome的search就可以全局搜索,但是有时候会有一些迷之搜不到的情况……

伪造发送端

在chrome的Global Listeners处看看当前页面存在几个监听器,打断点。使用window.postMessage(poc,'*')去调就可以。

trick

  1. 有的时候全局搜索addEventListener会发现代码中存在但是却没有注册为Listener的情况,这个时候应该是没有触发响应的逻辑,具体什么时候会触发现在我也没有找到一定的规律(业务点)。
  2. attachEvent('onmessage',funciton(e){})也会处理响应的message数据,建议也给断上。
  3. 对于无法是有iframe包裹的情况可以尝试使用使用以下几种方法
1
2
3
4
window.open
window.opener
<a>
form post打开的窗口
  1. window之间传送的消息如果是json的话有可能会有str2json的函数,这类函数中极有可能使用eval()
  2. message不仅仅可以传送string,同时可以传输object,传输过程中可能存在序列化和反序列化(挖CVE的同学划重点了 ps:个人瞎猜,不负责任)

写在后面

有的时候见到port1.postMessage(message)的情况,不要奇怪,这是使用了messageChannel。messageChannel简单的说就是在两个窗口间创建一个通道,可以方便的发送消息。没有深入研究有没有伪造的可能。

更新: 看了一眼messageChannel感觉没什么新东西,在特定场景有可能会出漏洞。比如其中一个端口是window.parent。(看了一个比喻,说messageChannel是土电话,很贴切了,哈哈哈哈哈哈哈哈)

对于现在手上的项目进行挖掘后并没有很喜人的进展,我太菜了Orz…….
等我挖到洞相关洞了再更一波~

扫描二维码,分享此文章