Avoiding Clickjacking Attacks

A configuration guide for developers.

To prevent your website from being vulnerable to Clickjacking attacks, you must add set specific values for HTTP headers to specify which sites are allowed to embed your site.

For the vast majority of cases, there will not be a need to allow other sites than your own to be embed it. For that reason, I either recommend restricting the allowed sites to same-origin, or if possible, letting nothing embed it. For what exactly same-origin refers to, this MDN article elaborates nicely.

In short, two sites are considered the same origin when their protocol (e.g. https), domain (example.com) and port (:443, usually omitted) all match. Subdomains will not match, so none of https://google.com, https://www.google.com or https://images.google.com are of the same origin.

There are two relevant headers you can set: Content-Security-Policy and X-Frame-Options.

Content-Security-Policy (CSP)

Content Security Policy (CSP for short), was primarily introduced (circa 2012) to protect against XSS (cross-site scripting) attacks. It is a HTTP header which instructs the browser on how to handle multiple attacks, such as which are valid domains to execute scripts from (protecting against XSS), and whether resources must have a SHA hash associated with it (to protect against supply chain attacks).

Here's an example policy for example.com, with each policy separated by semi-colons.

Content-Security-Policy: script-src scripts.example.com; default-src 'self'
  • script-src - scripts (e.g. javascript) are only allowed to be loaded if they come from scripts.example.com
  • default-src - if there's no policy defined for an object (e.g. img-src for images, full list here), they will use this value. 'self' means same-origin, so only images hosted on example.com will be allowed to load.

In 2014, CSP version 2 was published which added support for a few more attacks to cover - particularly relevant to us is its ability for a website to specify which sites (referred to as frame-ancestors) are allowed to host it. The overwhelming majority of browsers supported version 2 (and by extension, frame-ancestors) by 2017. So, it's something you can rely on.

As we're focussing on Clickjacking attacks, I'll just specify the pertinent frame-ancestors values you should be aware of. For a general guide on how to configure CSP to secure your site, refer the the OWASP CSP cheatsheet. A site can specify one or more values, each separated by spaces. Here are possible values for frame-ancestors:

  • 'none'
    • This means no sites are allowed to embed it.
  • 'self'
    • Only same-origin sites are allowed to embed it.
  • example.com
    • Only example.com can embed this site.
  • *.example.com
    • Only subdomains of example.com can embed this site. For example, embed.example.com, www.example.com, or www.nested.example.com
  • *
    • Any site is allowed to embed it. This is an unsafe value, and will allow arbitrary sites to embed yours.
  • *.com
    • Any site ending in .com is allowed to embed it. This is an unsafe value, and will allow arbitrary sites to embed yours.
    • Be careful with any wildcard value, you need to ensure that it's not a public suffix like .co.uk, as those are considered to be domains anyone can register.
  • https:
    • Any site using HTTPS is allowed to embed it. This is an unsafe value, and will allow arbitrary sites to embed yours.

In general, I'd recommend disabling embeds your site, so a value of 'none'. Otherwise, a safe bet (in case you need to embed your site on itself), is to only allow 'safe', and to add specific exceptions manually (e.g. allowing embed.example.com), but try to avoid wildcards where possible:

Content-Security-Policy: frame-ancestors 'self' embed.example.com

X-Frame-Options

This header was brought in a few years earlier than Content-Security-Policy (earliest implementation was 2011, majority support in 2013), specifically to protect against Clickjacking. It instructs browsers whether to reject all attempts to embed, or only allow embedding on itself.

This is a deprecated header, meaning that it's no longer recommended to use this header to disable embeds. It still works, but it may be removed at some point in the future. Pretty much every browser after 2017 supports the extension to Content-Security-Policy that allows setting frame-ancestors, so that should be used instead.

There are only two possible values to set for this header.

  • X-Frame-Options: DENY
    • DENY prevents any site from embedding your site, including your own.
  • X-Frame-Options: SAMEORIGIN
    • SAMEORIGIN prevents any site from embedding your site, except things from the same-origin (e.g. itself).

There is no harm adding this header to your config in addition to Content-Security-Policy. If you don't think your site does or will embed itself on the site, set it to DENY. Otherwise, if it's not a core requirement for your site to be embedded in an <iframe>, but you are likely to embed your own site on itself, then set it to SAMEORIGIN. More details on this header can be found on MDN.

What if I need my site to be embedded by anyone?

In the scenario where you need your website to be embedded by anyone, there's a few things you can do to mitigate this attack.

  • Try to isolate the embeddable portion to a separate domain. Prefer to avoid a subdomain, as cookies may unintentionally be valid for subdomains.
  • Avoid UI elements that are interactive, and can change backend state, to ensure the site is effectively read-only.
  • If the content does not require authentication (in other words, you do not need to be logged in to see the page), avoid authenticating them.

Conclusion

To protect against Clickjacking attacks, you must set some HTTP headers to prevent other sites embedding your own. Unfortunately, as the default is not to deny, it means there's a bunch of websites developed by people that are unaware of this attack that are vulnerable to it.

What's required to change the default behaviour of web-browsers to the much safer behaviour of by default allowing only same-origin embeds? If we change the behaviour there, we can make it secure by default, and add sufficient warning labels to the documentation describing how to enable it.

Until then, it's a matter of educating developers, and detecting pre-emptively when sites are vulnerable to this attack.