Combating ClickJacking With X-Frame-Options

Back in January of 2009, I announced IE8’s support for a new header-specified directive: X-Frame-Options, that can be used to mitigate ClickJacking attacks. As a declarative security measure, X-Frame-Options has minimal compatibility impact, but requires adoption by clients and servers in order to provide its security benefit.

Since its introduction in IE8, we’ve seen a number of sites and other browsers adopt support for this directive as a mechanism to prevent malicious framing of content (called “ClickJacking” or “UI Redress” attacks), since frame-busting scripts can be defeated.

Browser Support

You can determine if your browser supports the X-Frame-Options directive using this test page. When permission to frame is denied, some browsers (e.g. IE, Opera) will show a message that allows the user to safely open the target page in a new window. Other implementations (e.g. Chrome, Firefox, Safari) will simply render an empty frame.

Browsers Supporting X-Frame-Options

Standardization

In October 2013, informational RFC7034 was published which defines the X-Frame-Options header and its values.

Using X-Frame-Options in Web Applications

Web developers can send a HTTP header named X-FRAME-OPTIONS on HTML responses to restrict how the page may be framed. Note that this token must be sent as a HTTP Header, and the directive will be ignored if found in a META HTTP-EQUIV tag.

Token Values

The X-Frame-Options header may contain one of three tokens:

  • DENY
  • SAMEORIGIN
  • ALLOW-FROM origin 

If the X-FRAME-OPTIONS value contains the token DENY, browsers will prevent the page from rendering if it will be contained within a frame. For instance, if https://shop.example.com/confirm.asp contains a DENY directive, that page will not render in a subframe, no matter where the parent frame is located.

If the value contains the token SAMEORIGIN, the browser will block rendering only if the origin of the top-level browsing-context is different than the origin of the content containing the X-FRAME-OPTIONS directive.  For instance, if https://shop.example.com/confirm.asp contains the X-FRAME-OPTIONS directive with the SAMEORIGIN token, the page may be framed by any page from the exact https://shop.example.com origin.

If the value contains the token ALLOW-FROM origin,the browser will block rendering only if the origin of the top-level browsing context is different than the origin value supplied with the Allow-From directive. For instance, if https://shop.example.com/confirm.asp contains the X-FRAME-OPTIONS directive with the value Allow-From https://partner.affiliate.com, then the page may be framed only by pages from the https://partner.affiliate.com origin.

Note that the Allow-From token does not support wildcards or listing of multiple origins. For cases where the server wishes to allow more than one page to frame its content, the following design pattern is recommended:

  1. The outer IFRAME supplies its own origin information, using a querystring parameter on the Inner IFRAME's src attribute; e.g. https://sitetoframe/pagetoframe.asp?ForOrigin=HostSite.com. That querystring value can obviously be specified by an attacker, but that's fine.
  2. The server for the inner IFRAME verifies the supplied FromOrigin information meets whatever criteria business practices call for. For example, the server that serves the IFRAME containing a social network's "Like" button, might check to see that the supplied FromOrigin matches the FromOrigin expected for that Like button, and that the owner of the specified FromOrigin has a valid affiliate relationship, etc.
  3. If satisfied with the information supplied, the server for the Inner IFRAME sends an X-FRAME-OPTIONS: allow-from hostsite.com header
  4. The Browser then enforces the X-FRAME-OPTIONS directive.

If an attacker had specified a bogus origin in step #1 (different than the actual origin of the outermost page), he'd be blocked at step #4 when the browser actually enforces the Allow-From restriction.

There's a test page for these tokens here: https://www.enhanceie.com/test/clickjack/

Best Practices

  1. Send the content as an HTTP Header – the directive is ignored if specified in a META tag
  2. Use X-Frame-Options on critical configuration pages or other pages that require an “authentic user click”
  3. Don’t use “sameorigin” if you have any page on your domain which accepts an arbitrary URL to frame

As outlined in point #2, you must ensure that you send the X-Frame-Options directive for the pages that need it. This typically includes checkout or bank-transfer confirmation pages, pages that contain one-click purchase links, or pages that make permanent configuration changes. While you could send an X-Frame-Options directive for all of your site’s pages, this has the potential downside that it forbids even non-malicious framing of your content (for instance, when the user visits your site using a Google Image Search results page).

Point #3 requires some explanation-- The question of when to use DENY and when to use SAMEORIGIN is an interesting one. It comes down to the expected use case for the page protected with the directive. If you never expect the page to be framed, you should use DENY. If you expect the page to be framed only by pages on your server (e.g. it's part of a FRAMESET) then you'll want to use SAMEORIGIN.

Keep in mind that if a page specifies SAMEORIGIN, browsers will forbid framing only if the top-level origin FQDN (fully-qualified-domain-name, aka what you see in the address bar) does not exactly match FQDN of the subframe page that demanded the SAMEORIGIN restriction. Your critical pages should specify DENY if your site has a page that permits hosting of arbitrary frames. For instance, suppose your site has a page like: https://victimSite.com/FrameIt.asp?embedframe=//attacker.com/eviloverlay, where your page embeds a frame pointed at the URL specified in the query string.

If you were to specify the SAMEORIGIN directive on your victimsite.com/confirm.asp response, it would be vulnerable to ClickJacking by Attacker.com. The top-level page (victimsite.com/Frameit.asp) and the grandchild frame (victimsite.com/confirm.asp) would share the same origin, and thus the frame between top-level and the grandchild can ClickJack that grandchild.

Thanks for reading, and thanks to the other browsers for supporting this mechanism!

-Eric