views:

8829

answers:

17

Let's say you don't want other sites to "frame" your site in an <iframe>:

<iframe src="http://yourwebsite.com"&gt;&lt;/iframe&gt;

So you insert anti-framing, frame busting JavaScript into all your pages:

/* break us out of any containing iframes */
if (top != self) { top.location.replace(self.location.href); }

Excellent! Now you "bust" or break out of any containing iframe automatically. Except for one small problem.

As it turns out, your frame-busting code can be busted, as shown here:

<script type="text/javascript">
    var prevent_bust = 0  
    window.onbeforeunload = function() { prevent_bust++ }  
    setInterval(function() {  
      if (prevent_bust > 0) {  
        prevent_bust -= 2  
        window.top.location = 'http://server-which-responds-with-204.com'  
      }  
    }, 1)  
</script>

This code does the following:

  • increments a counter every time the browser attempts to navigate away from the current page, via the window.onbeforeonload event handler
  • sets up a timer that fires every millisecond via setInterval(), and if it sees the counter incremented, changes the current location to a server of the attacker's control
  • that server serves up a page with HTTP status code 204, which does not cause the browser to nagivate anywhere

My question is -- and this is more of a JavaScript puzzle than an actual problem -- how can you defeat the frame-busting buster?

I had a few thoughts, but nothing worked in my testing:

  • attempting to clear the onbeforeunload event via onbeforeonload = null had no effect
  • adding an alert() stopped the process let the user know it was happening, but did not interfere with the code in any way; clicking OK lets the busting continue as normal
  • I can't think of any way to clear the setInterval() timer

I'm not much of a JavaScript programmer, so here's my challenge to you: hey buster, can you bust the frame-busting buster?

A: 

A simple, naive and probably wrong thing:

if (top != self)
{
  // super obfuscated buster-buster-buster code
  window.onbeforeunload = null;
  top.location.replace(self.location.href);
}

disclaimer:

I know nothing about web programming or Javascript

1800 INFORMATION
does not work. try it.
Jeff Atwood
which, I also pointed out IN THE POST BODY. Just sayin'.
Jeff Atwood
Obviously, I didn't read that far
1800 INFORMATION
+1  A: 

Well, you can modify the value of the counter, but that is obviously a brittle solution. You can load your content via AJAX after you have determined the site is not within a frame - also not a great solution, but it hopefully avoids firing the on beforeunload event (I am assuming).

Edit: Another idea. If you detect you are in a frame, ask the user to disable javascript, before clicking on a link that takes you to the desired URL (passing a querystring that lets your page know to tell the user that they can re-enable javascript once they are there).

Edit 2: Go nuclear - if you detect you are in a frame, just delete your document body content and print some nasty message.

Edit 3: Can you enumerate the top document and set all functions to null (even anonymous ones)?

RedFilter
+4  A: 

I think you were almost there. Have you tried:

window.parent.onbeforeunload = null;
window.parent.location.replace(self.location.href);

or, alternatively:

window.parent.prevent_bust = 0;

Note: I didn't actually test this.

Jeff Meatball Yang
I edited your code sample (the test for parent seems to fail) but the edited version DOES appear to work!
Jeff Atwood
Cool. It's always tricky to answer with untested code - I do it to at least get the idea across - and let the poor asker debug. :)
Jeff Meatball Yang
Won't work if parent is on a different domain, which is likely the case!
Josh Stodola
+18  A: 

Came up with this, and it seems to work at least in Firefox.

if(top != self) {
 top.onbeforeunload = function() {};
 top.location.replace(self.location.href);
}
Jani Hartikainen
both Jani and Jeff's solution (once edited) are correct and work equivalently; giving Jani the accept because his solution worked right without any editing
Jeff Atwood
This will only work if the two windows are of the same domain; a rare occurrence when you want to escape from a frame.
J-P
If there are nested frames involved, you'll have to walk the frame chain and remove all `onbeforeunload` handlers, not just the one on top!
Christoph
important clarification: this worked for me because the iframe src= was being set dynamically, and thus the cross-domain policy was NOT in effect. J-P is absolutely right, in a static src= this wouldn't work.
Jeff Atwood
ok now can someone come up with a frame buster buster buster buster?
Epaga
Jeff, are you trying to say that setting the SRC to a different domain programmatically bypasses the cross-domain policy? If so, that seems like a ridiculous bug that you certainly should not rely on.
Josh Stodola
Additionally, if that is true, all the evil site has to do to counteract this is set the SRC on the server-side instead...?
Josh Stodola
A: 

In a real world scenario involving different domain names you can't touch other window contexts anyways. You don't even have read access to the location property. Am I missing something here? Or are you guys doing your tests from a single domain name?

`window.location` is normally (at least partially) excluded from the same-origin policy, but as J-P mentioned, setting `window.onbeforeunload` across domains will most likely fail
Christoph
A: 

What about calling the buster repeatedly as well? This'll create a race condition, but one may hope that the buster comes out on top:

(function() {
    if(top !== self) {
     top.location.href = self.location.href;
     setTimeout(arguments.callee, 0);
    }
})();
Christoph
+4  A: 

Ok, so we know that were in a frame. So we location.href to another special page with the path as a GET variable. We now explain to the user what is going on and provide a link with a target="_TOP" option. It's simple and would probably work (haven't tested it), but it requires some user interaction. Maybe you could point out the offending site to the user and make a hall of shame of click jackers to your site somewhere.. Just an idea, but it night work..

+32  A: 

I'm not sure if this is viable or not - but if you can't break the frame, why not just display a warning. For example, If your page isn't the "top page" create a setInterval method that tries to break the frame. If after 3 or 4 tries your page still isn't the top page - create a div element that covers the whole page (modal box) with a message and a link like...

You are viewing this page in a unauthorized frame window - (Blah blah... potential security issue)

click this link to fix this problem

Not the best, but I don't see any way they could script their way out of that.

Hugoware
I have tried this and this works. Another piece that I like about this solution is that it brings to light to the user what kind of site he/she was on before going to your content.Sample Code:if (parent.frames.length > 0) { top.location.replace(document.location); setTimeout(function() { if (parent.frames.length > 0) { document.location = "http://www.google.com"; } }, 10);}
pope
Not only is this a good way of avoiding abuse, it's pretty friendly to sites who may want to iframe your site just to take a peek at it, though not to allow use of it. Ideally, I think a screenshot of the site's homepage should be used, with some explanation of why it can't be used in the iframe overlaid on top.
wheresrhys
+6  A: 

After pondering this for a little while, I believe this will show them whose boss...

if(top != self) {
  window.open(location.href, '_top');
}

Using "_top" as the target parameter for window.open will launch it in the same window.

Josh Stodola
this doesnt help because it would just annoy the average user who have no idea wtf frames are, and they see your site, logo etc, and see that it sucks due to the alerts. you have to make it work without user intervention/annoyance.
Chii
LOL, the alert loop was a joke dude. Lighten up.
Josh Stodola
+2  A: 

All the proposed solutions directly force a change in the location of the top window. What if a user wants the frame to be there? For example the top frame in the image results of search engines.

I wrote a prototype where by default all inputs (links, forms and input elements) are disabled and/or do nothing when activated.

If a containing frame is detected, the inputs are left disabled and a warning message is shown at the top of the page. The warning message contains a link that will open a safe version of the page in a new window. This prevents the page from being used for clickjacking, while still allowing the user to view the contents in other situations.

If no containing frame is detected, the inputs are enabled.

Here is the code. You need to set the standard HTML attributes to safe values and add additonal attributes that contain the actual values. It probably is incomplete and for full safety additional attributes (I am thinking about event handlers) will probably have to be treated in the same way:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
  <head>
    <title></title>
    <script><!--
      function replaceAttributeValuesWithActualOnes( array, attributeName, actualValueAttributeName, additionalProcessor ) {
        for ( var elementIndex = 0; elementIndex < array.length; elementIndex += 1 ) {
          var element = array[ elementIndex ];
          var actualValue = element.getAttribute( actualValueAttributeName );
          if ( actualValue != null ) {
            element[ attributeName ] = actualValue;
          }

          if ( additionalProcessor != null ) {
            additionalProcessor( element );
          }
        }
      }

      function detectFraming() {
        if ( top != self ) {
          document.getElementById( "framingWarning" ).style.display = "block";
        } else {
          replaceAttributeValuesWithActualOnes( document.links, "href", "acme:href" );

          replaceAttributeValuesWithActualOnes( document.forms, "action", "acme:action", function ( form ) {
            replaceAttributeValuesWithActualOnes( form.elements, "disabled", "acme:disabled" );
          });
        }
      }
      // -->
    </script>
  </head>
  <body onload="detectFraming()">
    <div id="framingWarning" style="display: none; border-style: solid; border-width: 4px; border-color: #F00; padding: 6px; background-color: #FFF; color: #F00;">
      <div>
        <b>SECURITY WARNING</b>: Acme App is displayed inside another page.
        To make sure your data is safe this page has been disabled.<br>
        <a href="framing-detection.html" target="_blank" style="color: #090">Continue working safely in a new tab/window</a>
      </div>
    </div>
    <p>
      Content. <a href="#" acme:href="javascript:window.alert( 'Action performed' );">Do something</a>
    </p>
    <form name="acmeForm" action="#" acme:action="real-action.html">
      <p>Name: <input type="text" name="name" value="" disabled="disabled" acme:disabled=""></p>
      <p><input type="submit" name="save" value="Save" disabled="disabled" acme:disabled=""></p>
    </form>
  </body>
</html>
Johan Stuyts
The problem with this is the frame-maker could use position:absolute to place active button on top of your inactive buttons and the user will just see your webpage and think they are clicking YOUR buttons.
jmucchiello
The warning message would still be shown, but of course it is easy to cover the link to the safe page as you suggest. But why go through all the trouble of framing my page to get people to click on a familiar button if you can simply copy the page and achieve the same effect?The code above mainly prevents clickjacking. If you show my page invisibly above another page it isn't possible to invoke actions on my site.
Johan Stuyts
+6  A: 

http://coderrr.wordpress.com/2009/06/18/anti-anti-frame-busting/

if (top != self) {  
  top.location.replace(document.location)  
  alert('busting you out, please wait...')  
}
+4  A: 
if (top != self) {
  top.location.replace(location);
  location.replace("about:blank"); // want me framed? no way!
}
Does this work? Has it been tested?
thomasrutter
+1  A: 

If you add an alert right after the buster code, then the alert will stall the javascript thread, and it will let the page load. This is what StackOverflow does, and it busts out of my iframes, even when I use the frame busting buster. It also worked with my simple test page. This has only been tested in Firefox 3.5 and IE7 on windows.

Code:

<script type="text/javascript">
if (top != self){
  top.location.replace(self.location.href);
  alert("for security reasons bla bla bla");
}
</script>
Marius
A: 

If you want to test your buster buster buster, I made a page that frames any given URL.

Ivo Danihelka
A: 

setInterval and setTimeout create an automatically incrementing interval. Each time setTimeout or setInterval is called, this number goes up by one, so that if you call setTimeout, you'll get the current, highest value.

   var currentInterval = 10000;
   currentInterval += setTimeout( gotoHREF, 100 );
   for( var i = 0; i < currentInterval; i++ ) top.clearInterval( i );
   // Include setTimeout to avoid recursive functions.
   for( i = 0; i < currentInterval; i++ )     top.clearTimeout( i );

   function gotoHREF(){
           top.location.href = "http://your.url.here";
   }

Since it is almost unheard of for there to be 10000 simultaneous setIntervals and setTimeouts working, and since setTimeout returns "last interval or timeout created + 1", and since top.clearInterval is still accessible, this will defeat the black-hat attacks to frame websites which are described above.

Christopher W. Allen-Poole
A: 

If you look at the values returned by setInterval() they are usually single digits, so you can usually disable all such interrupts with a single line of code:

for (var j = 0 ; j < 256 ; ++j) clearInterval(j)
Robin Nixon
+5  A: 

FWIW, most latest-version browsers support the X-Frame-Options: deny directive, which works even when script is disabled.

IE8:
http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx

Firefox (unreleased?)
https://bugzilla.mozilla.org/show_bug.cgi?id=475530

Chrome (unreleased?)
http://blog.chromium.org/2010/01/security-in-depth-new-security-features.html

EricLaw -MSFT-
excellent, supporting this in the browser .exe is the way to go without a doubt. When you say "most browsers", which ones specifically? I can't find good sources for anything but IE8.
Jeff Atwood
Here's a test page: http://www.enhanceie.com/test/clickjack/. Chrome 4.1.249.1042 supports. Opera 10.50 supports. Firefox 3.6.2 does NOT yet support. Safari 4.0.3 supports.
EricLaw -MSFT-
Firefox 3.6.9 will support it natively ( http://hackademix.net/2010/08/31/x-frame-options-finally-on-vanilla-firefox/ ) and any Firefox install with NoScript has had it since the beginning of 2009 ( http://hackademix.net/2009/01/29/x-frame-options-in-firefox/ )
ssokolow