Fine-grained control over framing (2/2)


Authenticating the framer

Continuing on the list of error-prone kludges required before the X-Frame-Options header acquired its ability to explicitly name another website as authorized framer:

  • Direct hand off with federated authentication. This assumes the user is logged into both the framer and framee with the same identity (for example an OpenID login where one side is the identity provider, and the other one is a relying party) The framer can pass a signed message to the framee containing a time-stamp, URL of the page being framed and identity of the user. Framee verifies this signature and compares the identity asserted in the message with the identity authenticated from the request. (Generalizing this slightly, it is not necessary for both sides to recognize the user with same identity, as long as the identities can be compared. For example, the user could have different pseudonyms at framer and framee, but as long as one side knows the pseudonym on the other side, this solution works.)
  • In the absence of shared identity, an improvised redirect scheme can be used as last resort. (To paraphrase Wheeler, most problems in web security can be solved by adding another layer of redirection.)
    • Container includes a query string parameter C identifying itself when linking to the framed content.
    • Framee looks at incoming parameter C. If it is among the containers permitted to frame this page, it sets a session cookie containing { R, C } where R is a random challenge, and redirects back to the container with R.
    • Container notices the challenge, determines that it was indeed trying to frame content for this user and redirects back to framee, this time with { R, C } as query string parameters.
    • Framee notices the presence of the session cookie, and compares the R in the cookie against the one in the query string . If these are identical, it deletes the cookie and returns the full content intended to be rendered in the frame.

Strictly speaking these kludges do not verify that the framer is one of the expected websites– only that it had the cooperation of one of those websites. For example in the first scheme it is possible for a dishonest container to create signed assertions for one of its own URLs and then hand them off to another site served from a completely different URL.

The catch with the cache

Another curious property: these schemes stop the HTTP response from being returned to the browser when framing conditions are incorrect. In contrast the X-Frame-Options mechanism does not suppress the response– in fact it must be returned along with the regular HTTP response– but relies on the browser to block rendering of content when the intended framing conditions are not satisfied. This is a crucial difference, and at first blush looks like an advantage, in that not serving content seems safest option. But caching can can throw a wrench into that. Once the framed content is served to an authorized container in a cacheable manner, future references from other containers will load it straight from the cache, bypassing any checks preformed during initial fetch. This makes it critical that any page emitting an ALLOW conditionally based on a query-string parameter ensure that the same page can not be loaded out of the cache by a different website trying to frame it. In the examples above, the first protocol achieves this by making the URL unique to each user based on their authenticated identity. The second option relies on the unpredictability of random value R and its existence as session cookie for that user.

Caching is also the basis for confusion in the X-Frame-Options RFC, in section 2.3.2.4:

Caching the X-Frame-Options header for a resource is not recommended.
[...]
However, we found that none of the major browsers listed in
Appendix A cache the responses.

Both statements are misleading. X-Frame-Options header is in fact cached along with the response and “replayed” from the cache just like Content-Options and many other HTTP response headers are. Major browsers including Internet Explorer, Firefox and Chrome all implement this correctly. (Of course if the resource itself is not cacheable according to Cache-control header, nothing will be stored. But there is no intermediate scenario where only the payload is stored but the header is stripped out.) It is easy to verify that web browsers are handling this correctly: here is a page with multiple test-cases for X-Frame-Options that includes one example with cacheable content.

In fact not storing X-Frame-Options for a cached resource would lead to a vulnerability and allow by-passing the restrictions:

  • Load the target resource in a top-level document, such as a new window, causing it to be cached by the client
  • Close that window and then reload the resource in a frame inside the malicious website.
  • Because the web browser retrieves the page out of the cache minus its stripped X-Frame-Options header (in this hypothetical implementation) it does not realize that framing is disallowed. The content renders correctly inside the iframe, creating a potential clickjacking vulnerability.

The problematic case the RFC tried to warn about is when ALLOWFROM is used to distinguish between multiple trusted framers. Suppose that foo.com and bar.com are both allowed to frame a cacheable resource served by a web-site. If this page is returned with ALLOWFROM=foo.com and later loaded by bar.com from the cache, it will not render. The cached header is granting access to the wrong framer.

Web browsers can’t divine when this problematic scenario arises. Looking at a single response containing ALLOWFROM directive, the browser does not know if there are multiple other authorized framers in addition to the presently named one. In the above example if foo.com was the only authorized website, there would be no problem with caching that restriction. Only the website has visibility into that logic. At best an RFC can point out this scenario and recommend that servers mark such responsesĀ  non-cacheable. Realistically this is an edge-case: ALLOWFROM is not supported by Chrome. In fact a perfectly good patch submitted for implementing this was deliberately declined in favor of incorporating the functionality into Content Security Policy at some unspecified future date. In the absence of equivalence between major browsers, few websites can rely on ALLOWFROM exclusively for conditional framing.

Deciding after page-load

One can also devise workarounds where content is returned and rendered but somehow deactivated until the identity of the framer is established. For example all the UI elements may be disabled or the entire page obscured by an opaque div. The access check is performed in Javascript, based on a postMessage notification from the container. Since the message received includes the identity of the sender as vouched for by the web browser itself, the framed content can activate its UI based on the origin. These schemes do not suffer from caching problems as the access check is done every time the frame is loaded.

CP

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s