Protecting Your Users Against Clickjacking

Clickjacking attacks trick web users into performing an action they did not intend, typically by rendering an invisible page element on top of the action the user thinks they are performing.

Clickjacking won’t affect your site directly, but it could potentially affect your users. And only you can protect them!

Risks

Prevalence Occasional
Exploitability Easy
Impact Harmful

What could a determined hacker do with a clickjacking attack?

Our example hack tricked the user into “Liking” an item on Facebook. Clickjacking has also been used in the past to:

  • Harvest login credentials, by rendering a fake login box on top of the real one.
  • Trick users into turning on their web-cam or microphone, by rendering invisible elements over the Adobe Flash settings page.
  • Spread worms on social media sites like Twitter and MySpace.
  • Promote online scams by tricking people into clicking on things they otherwise would not.
  • Spread malware by diverting users to malicious download links.

Protection

Clickjacking attacks wrap a page the user trusts in an iframe, then renders invisible elements on top of the frame. To ensure that your site doesn’t get used in a clickjacking attack, you need to make sure it cannot be wrapped in an iframe by a malicious site. This can be done by giving the browser instructions directly via HTTP headers, or in older browser by using client-side JavaScript (frame-killing).

Content Security Policy

The Content-Security-Policy HTTP header is part of the HTML5 standard, and provides a broader range of protection than the X-Frame-Options header (which it replaces). It is designed in such a way that website authors can enumerate individual domains from which resources (like scripts, stylesheets, and fonts) can be loaded, and also domains that are permitted to embed a page.

To control where your site can be embedded, use the frame-ancestors directive:

Content-Security-Policy: frame-ancestors 'none'
The page cannot be displayed in a frame, regardless of the site attempting to do so.
Content-Security-Policy: frame-ancestors 'self'
The page can only be displayed in a frame on the same origin as the page itself.
Content-Security-Policy: frame-ancestors *uri*
The page can only be displayed in a frame on the specified origins.
X-Frame-Options

The X-Frame-Options HTTP header is an older standard that can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object> tag. It was designed specifically to help protect against clickjacking, but has since between made obsolete by content security polices.

There are three permitted values for the header:

DENY The page cannot be displayed in a frame, regardless of the site attempting to do so.
SAMEORIGIN The page can only be displayed in a frame on the same origin as the page itself.
ALLOW-FROM *uri* The page can only be displayed in a frame on the specified origins.
Frame-Killing

In older browsers, the most common way to protect users against clickjacking was to include a frame-killing JavaScript snippet in pages to prevent them being included in foreign iframes. You might still see code like the following in legacy web applications:

<style>
  /* Hide page by default */
  html { display : none; }
</style>

<script>
  if (self == top) {
    // Everything checks out, show the page.
    document.documentElement.style.display = 'block';
  } else {
    // Break out of the frame.
    top.location = self.location;
  }
</script>

An example frame-killing script: when the page loads, this code will check that the domain of the page matches the domain of the browser window, which will not be true when the page is embedded in an iframe.

Most sites don’t need to be embedded in iframes, so a frame-killing script is easy to implement. If embedding is required in your application, consider adding an allowlist of domains, so you have control over where your content is embedded.

Frame-killing offers a large degree of protection against clickjacking, but it can be error-prone. Be sure to set appropriate HTTP headers as the first recourse in protecting your site.

Code Samples

The code samples below illustrate how to implement frame-killing in JavaScript, and how to set the HTTP headers mentioned above in various languages and web frameworks.

<style>
  /* Hide page by default */
  html { display : none; }
</style>

<script>
  if (self == top) {
    // Everything checks out, show the page.
    document.documentElement.style.display = 'block';
  } else {
    // Break out of the frame.
    top.location = self.location;
  }
</script>
Django

response = render_to_response("template.html", {}, context_instance=RequestContext(request))
response['Content-Security-Policy'] = "frame-ancestors 'none'"
response['X-Frame-Options'] = 'DENY'
return response

Rails

response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
response.headers['X-Frame-Options'] = 'DENY'


public void doGet(HttpServletRequest request, HttpServletResponse response)
{
  response.addHeader("Content-Security-Policy", "frame-ancestors 'none'");
  response.addHeader("X-Frame-Options", "DENY");
}


Response.AppendHeader("Content-Security-Policy", "frame-ancestors 'none'");
Response.AppendHeader("X-Frame-Options", "DENY");


response.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
response.setHeader("X-Frame-Options", "DENY");


header("Content-Security-Policy: frame-ancestors 'none'", false);
header("X-Frame-Options: DENY");

Further Reading