README


home
@j4cob

Emulate X-Frame-Options: ALLOW-FROM using postMessage

17 May 2014

Clickjacking is a a web-based attack that can be mounted on a website when the attacker is able to display that website inside an iframe on another website. In short, the attacker tricks users into clicking a button on their target site, e.g. [delete account] or [tweet].

Websites prevent clickjacking by sending the X-Frame-Options: SAMEORIGIN header, which tells browsers not to display their pages inside an iframe on any site other than their own. Since the iframe doesn’t display anything, there’s nothing to click.

However, sometimes a site want to allow limited framing by other hostnames (origins). For instance, Twitter shows extended tweet information from cards.twitter.com in an iframe loaded on twitter.com. Since there are sometimes buttons inside those cards, like ‘share this,’ they want to make sure cards.twitter.com can only be displayed by twitter.com.

The X-Frame-Options header has another setting: X-Frame-Options: ALLOW-FROM . This allows, e.g. cards.twitter.com to serve ‘X-Frame-Options: ALLOW-FROM https://twitter.com’. That prevents iframing by third parties but not by twitter.com itself. Unfortunately, ALLOW-FROM is only supported on Firefox and IE. Eventually ALLOW-FROM will be supplanted by CSP’s frame-ancestors directive, but that’s not ready for prime time yet.

So, what can we do today? It turns out you can get the equivalent ALLOW-FROM behavior today if you’re willing to require Javascript. We start with the antiClickjack CSS approach from Rydstedt, Burzstein, Boneh, and Jackson, described in short at OWASP and with more detail in the original paper, “Busting Frame Busting”. On top of that, we require the host frame to send a postMessage to the child frame. The content of the message doesn’t matter. The child frame will check the ‘origin’ attribute of the received message, which is guaranteed by the browser, against a list of acceptable parent frames. If the sending origin is acceptable, the child frame removes the antiClickjack style node, and the content (including buttons) becomes visible. If the sending origin is not acceptable, the child frame does nothing and the content remains hidden and non-interactable.

Note: It’s important that the parent frame is not itself frameable. Typically that means the parent frame sends X-Frame-Options: SAMEORIGIN, though one could also implement the ALLOW-FROM strategy recursively if necessary.

You can see an example in action at xfoaf1.crud.net. It looks like this:

Parent frame:

function loadFrame(url) {                                                                                                                                      
  var ifr = document.createElement('iframe');                                                                                                                  
  ifr.src = url;                                                                                                                                               
  ifr.onload = function() {                                                                                                                                    
    // Send a dummy message just to prove we're here.                                                                                                          
    ifr.contentWindow.postMessage("openSesame", "*");                                                                                                          
  }                                                                                                                                                            
  document.body.appendChild(ifr);                                                                                                                              
};

**Child frame**:
<style id="antiClickjack">body{display:none !important;}</style>                                                                                               
                                                                                                                                                               
<script type="text/javascript">                                                                                                                                
   var validParents = {                                                                                                                                        
     "http://xfoaf1.crud.net": 1                                                                                                                               
   };                                                                                                                                                          
   window.addEventListener("message", function(message) {                                                                                                      
     if (validParents[message.origin] && message.data == "openSesame") {                                                                                       
       var antiClickjack = document.getElementById("antiClickjack");                                                                                           
       antiClickjack.parentNode.removeChild(antiClickjack);                                                                                                    
     }                                                                                                                                                         
   }, false);                                                                                                                                                  
</script>