Posts | Archive

How I Almost Won Pwn2Own via XSS

No, seriously.

The good: Google has patched a serious vulnerability I discovered in the Android web market.

The bad: Since the Android web market was launched earlier this year, it was possible to remotely install arbitrary applications with arbitrary permissions onto a victim's phone simply by tricking them into clicking a malicious link (either on their desktop OR phone).  The exploit works universally across all Android devices, versions, and architectures.

The ugly: Not anticipating that the vulnerability would qualify for Pwn2Own, I prematurely reported the vulnerability to Google.  Besides the order of magnitude difference in monetary payout (\$1337 from Google vs. \$15,000 from ZDI), I'm disappointed since I thought it would be super hilarious to win Pwn2Own with a lame XSS vuln. :-P

The Vulnerability

The actual vulnerability was an incredibly low-hanging naive persistent XSS in the Android web market.  When posting an Android application through the publishing interface, there's a description field for you to describe your application:


And yes, when that description is output to a user browsing the Android web market via the application details, a search result, your developer page, and a number of other vectors, the XSS payload contained in the description field will be executed within the context of the victim's browser:


The Exploit

So how does an XSS result in arbitrary code execution on your mobile device? Well, the Android web market allows you to install new applications directly to your mobile device while browsing the market on your desktop.  For more information on how Google is able to perform this remote install of applications on your device, check out my previous posts on the GTalkService and INSTALL_ASSET functionality.

While being able to browse the Android market via your browser on your desktop and push apps to your device is a great win for user experience, it opens up a dangerous attack vector.  Any XSS vulnerabilities in the web market allow an attacker to force your browser into making a POST request that triggers an app installation to your phone.

Since there is no on-device prompt or confirmation for these INSTALL_ASSET requests pushed to your phone, an attacker can silently trigger an malicious app install simply by tricking a victim into clicking a link while logged in to their Google account on their desktop or on their phone.  The malicious app delivered to the victim's phone can use any and all Android permissions, allowing for all sorts of evil behavior.

An app install can be triggered in the XSS payload via a simple AJAX POST to the /install URI, formatted as follows:

/* silently install malicious app to victim phone */
$.post('/install', {
    id: 'com.attacker.maliciousapp',
    device: initProps['selectedDeviceId'],
    token: initProps['token'],
    xhr: '1' }, function(data) {

Simply installing the app does not result in code execution since apps do not auto-start upon install on Android.  However, we can easily emulate this functionality effectively to auto-start our app and gain code execution.

One way, as Thomas mentions on his blog, is to have your installed app register for the PACKAGE_ADDED broadcast intent.  This will trigger your app and allow execution whenever a new app is installed.  Since we still control the users browser via our XSS, we can simply trigger the install of an additional application, which will result in our original malicious app being executed by the phone.

Alternately, if our XSS is taking place within the browser of the mobile device itself (like it would for the Pwn2Own contest, as opposed to occurring in the victim's desktop browser), we can simply insert a hidden IFRAME in our XSS payload, continually set the src of the IFRAME to something like "trigger://blah", and then have our installed malicious app register an intent filter on the "trigger://" URI scheme:

/* append hidden iframe */
$('body').append($('<iframe id="xss" width="0" height="0">'));

/* continually trigger iframe src */
function trigger() {
    $('#xss').attr('src', 'trigger://blah');
    setTimeout('trigger()', 1000);
setTimeout('trigger()', 1000);

This will cause our malicious app to be triggered and gain code execution as soon as it is finished installing.

The Vuln Reporting Fail

I had known the Android web market was coming for quite some time due to my investigation of the INSTALL_ASSET functionality and some private confirmation from Google folks, so this attack vector has actually been in my head for the better part of a year.  But when the Android web market was finally launched, I didn't get a chance to poke at it for a month or so.  Since I didn't hear about any reports of scary XSS, I had mostly assumed that others had already probed this vector or that Google had locked it down.

So it was quite a surprise that, when I finally found some free time, I was able to go from firing up Eclipse to landing trivial XSS in less than an hour.  I immediately reported the issue to Google Security.  Whoopsy daisy.  I blame my lack of critical thinking on three things: (1) I was excited to finally have an Android vuln that qualified for their security bounty (the previous ones were in on-device software which doesn't qualify); (2) I didn't think it would qualify for Pwn2Own since it would require the user to be logged in to their Google account on the mobile browser (since Pwn2Own doesn't take into account "cross-device" attacks); and (3) even if it did qualify, it was such low-hanging fruit it probably wouldn't survive until the contest.

Turns out, it does qualify for Pwn2Own. So yeah...I killed my own Pwn2Own bug.

Since Google rolled out the fix a week or so ago, I've been pounding on in hopes of exposing another XSS vulnerability that would be eligible for Pwn2Own.  While I'm no websec wizard, I recruited a handful of folks to help out in the effort who are websec wizards (thanks to those who helped out!).  Unfortunately, Google had significantly tightened things up and we've been unable to expose any additional XSS in time for the Pwn2Own contest at CanSecWest this week.

So while I'm missing out on a good sum of money by not winning the Pwn2Own contest, Google did award a bounty of \$1337 for reporting the vulnerability.  I'm more disappointed that I won't be able to win Pwn2Own with a lame XSS, which would be absolutely hilarious since Pwn2Own usually brings out the most exciting and technical exploits of the year.


There's a few take-away lessons from this story:

  • First, the Android web market opens up a serious attack vector by turning any XSS hole into remote on-device code execution.  At the very least, Google needs to add an on-device confirmation prompt for any non-device-initiated app installs (eg. any INSTALL_ASSET messages with the server-initiated flag set).
  • Second, despite how lame this particular XSS is, there's something to be said for these "cross-device" vulnerabilities that we'll likely see more of in the future as our desktops, mobile devices, TVs, appliances, and "the cloud" become even more interconnected.
  • Lastly, don't be stupid with your disclosures. :-)

So that's it for now.  I have a few more interesting XSS vectors to test but since they require some manual approval/intervention on Google's side, it's unlikely they'll be completed in the next couple days in time for Pwn2Own.  If anyone has any XSS in their backpocket, drop me a line. ;-)

Copyright © 2015 - Jon Oberheide <jon at oberheide dot org>.