How to circumvent the latest browser security policies
Published 2013-8-3DRAFT
this post isn't yet complete
This is mostly in regards to Facebook, but will probably work on other sites - twitter, google+, etc - equally well.
I'm working on http://bookmarkleteer.com, which is pretty simple concept - take some script, inject it on a page, have a good time.
Although most useful for things like Katamari Damacy and Destroy the Web
Something like Destroy the Web is best played on facebook (or any site with a boat ton of seemlingly never-ending content), but sadly, Facebook blocks every imaginable form of cross-domain communication!
It's ironic, seeing as how they exploit every form of cross-domain communication for their widgets on other sites. But they've been doing it for years so they're the perfect example of being very aware of what security holes exist and how to exploit them and, in this case, how to defend against them!
Protection
HTTPS
By defaulting to HTTPS, you automatically prevent scripts from loading on non-http sites.
That's not too big of a deal though, you might be able to find a way to host your scripts on some https site or just take literally 15 minutes to get your own certificate through Comodo or a little longer through StartSSL
Bam now you don't have that restriction.
Headers: strict-transport-security
strict-transport-security: max-age=60
Require that the connection be upgraded to HTTPS or the browser should refuse to serve the content.
Headers: content-security-policy
content-security-policy:default-src *;script-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* chrome-extension://lifbcibllhkdhoafpjfnlhfpfgnpldfl 'unsafe-inline' 'unsafe-eval' https://*.akamaihd.net http://*.akamaihd.net;style-src * 'unsafe-inline';connect-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.spotilocal.com:* https://*.akamaihd.net ws://*.facebook.com:* http://*.akamaihd.net https://fb.scanandcleanlocal.com:*;
This basically enumerates every possible resource from which a script may be loaded. Fortunately bookmarklets aren't blocked by this.
The only other potential exploit that I can see here (not entirely understanding the gibberish) is if you happen to have access to a localhost server on the user's machine that you can use for communication.
Read more on developer.mozilla.org and github.com/blog
Headers: x-content-type-options
x-content-type-options:nosniff
This prevents internet explorer from interpretting text/plain
as html
or image/jpef
as gif
when the server is sending the incorrect content type.
Headers: x-frame-options
x-frame-options:DENY
One thing I thought of was to load the bookmarklet, load an iFrame, redirect the iFrame to facebook
with escaped code in the href (i.e. www.facebook.com?something#encode-uri-escaped-code-here
),
but I couldn't do that either.
Headers: X-XSS-Protection
x-xss-protection:0
window.postMessage
// prevent any 3rd party sites from sending data back through a popup or iframe
window.addEventListener('message', function (ev) {
ev.stopPropagation();
ev.preventDefault();
return;
});
Getting around it
But here's how I got around it:
Getting the script on the page
Bookmarklet. I don't think there's any other way aside from the console - and you're certainly not getting many users to do that!
Sending Data:
iframe = document.createElement('iframe');
iframe.src = "//bookmarkleteer.com"
iframe.addEventListener('load', function () {
iframe.contentWindow.postMessage({}, iframe.src)
})
document.appendChild(iframe);
This works like a champ. Unfortunately, it's blocked in the other direction
Possibility:
Use the Like URL, which isn't iframe-deny protected:
Here's a typical Like widget (the kind you see on blogs and news sites):
It is not iframe-deny protected, so it's possible for me to communicate with that page from the page I'm at
iframe = document.createElement('iframe')
// then redirect to the Like url,
// but replace some of the arguments with arbitrary encodeURIComponent encoded script
iframe.src = "https://bookmarkleteer.com/whatever"
document.body.appendChild(iframe)
iframe.addEventListener('load', function () {
scriptText = decodeThing(iframe.contentWindow.location.href)
var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = scriptText;
s.parentNode.insertBefore(g, s);
});
The downside is that you have to find such a security hole on a case-by-case basis.
If you were actually trying to do something site specific, that's not as big of a problem though, because you'd be targetting a particular site anyway, but something generic like Destroy the Web isn't a great candidate for this approach.
Another way to Receive Data (works on all sites?):
As sucky and tedius as this may be, it's still a win:
document.createElement('img')
img = 'https://bookmarkleteer.com/favicon.ico';
img.addEventListener('load', function () {
console.log(img.width);
console.log(img.height);
})
If you used phrase-based programming approach you could perhaps communicate that 1
means $.get('/whatever')
and 2
means $.click('.thingy')
and so forth.
The width could be the function and the height the arguments.
By AJ ONeal
Did I make your day?
Buy me a coffee
(you can learn about the bigger picture I'm working towards on my patreon page )