[whatwg] Clickjacking and CSRF

In reply to Ian Hickson's call for comments from vendors[1], I wasn't subscribed at the time, so sorry for messing up thread status.

We agree that we need a solution quickly, and we are working on it. As IE has already implemented it's own header, the most pragmatic route would be to extend that to make it safe, and use that as a temporary stop gap while we work on a long term solution. It would be good to have a public reference spec for both, to ensure interoperability. This reply therefore contains two parts, a discussion on the x-frame-options header, and some thoughts on the larger picture.

The x-frame-options header:
===========================
This idea will work as a very specific workaround for clickjacking. It requires Web authors to opt-in, so browser vendors need to act in unison. We therefore plan to implement the same header as Microsoft has proposed, as well as to release a testsuite for this header. A couple of points regarding the implementation:

We would prefer not to allow this as a meta tag, for several reasons:
* By the time the meta reaches the client, the user might already have interacted with the document and clicked somewhere or XSS flaws might already have been exploited, so it gives a false sense of security. An attacker might be able to influence the loading time of the embedded document.
* Documents that flicker, first display contents, and later display a warning are confusing to users. Waiting to show contents until the entire document has loaded would severely impact performance on existing web pages, and is not an option.
* Meta tags cannot be included in many resources, for instance images and (most) objects.
* Meta tags would not work for some types of protection, for instance for resources that redirect when not logged in, and display a page otherwise - in such cases an attacker is often trying to check if a user is logged in to a service, so a convincing spoof against that service can be launched. Supporting meta headers might therefore give web authors a false sense of security. 

Every cross-domain resource must be checked for the header. Imagine the following scenario: Amazon allows an iframe (A) ad, which can script itself, but not the parent page. The ad displays some game, like "how many pushups can you do?". The ad hides an amazon checkout page in an iframe (B), using clickjacking techniques. If iframe B is not checked against the XSRF header, clickjacking can still occur. Note that iframe B is not cross-domain to the top page, only to its parent. A domain might have some resources that have liberal CSRF rules, and some that have strict rules. To ensure that iframe B cannot be pointing to a resource with liberal rules, which in turn points to an iframe (C) with strict rules, resources have to be checked against every ancestor all the way to the top document.

data: and javascript: URIs (as well as any other domain-less protocols) must be given the domain of their embedder (if any), in order to be checkable against the XSRF header. 

Redirect handling must be taken into account, every step in the redirect chain must be individually checked against the header, and redirection stop if the resource doing the redirect would not be allowed in a frame.

For the purpose of interoperability, the spec should state explicitly what same-origin vs third party means.
* Port: Over https, different ports can mean different certificates and thus servers with varying security levels. It might be prudent to differentiate on port.
* Protocol: One might want to allow https in https, but not http frames inside https. Differentiating on protocol might be wise.
* Sub-domains: Sub-domains should explicitly be mentioned not to be same-origin.
The disadvantage to differentiating between ports and protocols is that one would require additional header options in order to be able to include them in a server's policy.

The elements it blocks should be explicitly listed, frame, iframe, object, applet, embed, as well as other elements that allow scripting.

A long term solution
====================

Regarding clickjacking, we would like to see the problem in a larger context. Clickjacking is a type of CSRF attack, of which there are several.
* Clickjacking/cross-framing - where cross-site content is so obscured the user thinks he is interacting with content from a different site.
* Cross-posting - where one site can post data to another site and change the status of the user on the site. Typically many home routers are vulnerable to this, a POST to 192.168.1.1/ChangeDNSAddress could potentially change the setup of the user. Cross-posting includes any other non-indempotent methods such as PUT and DELETE.
* Cross-authentication - where one site can request data from another site, and that request is sent with authentication tokens. An example would be a bank which redirects to the login page when not logged in, and shows content otherwise, and the code <img src="https://www.mybank.com/protected/images/lock.gif" onerror="tryAnotherBank();" onload="SpoofAPasswordDialog();">

Note that many real world CSRF attacks will combine cross-posting and cross-authentication.

There is currently little protection against clickjacking, the x-frame-options is the first attempt.
Cross-posting is typically up to the content author to protect against, by including secret fields which cannot be guessed by an attacker. HTML 5 is working on a different protection scheme, where browsers will have to send a origin header with cross domain posting, and the web author can check for that header instead. That proposed solution cannot prevent cross-authentication though.
There is currently no protection against cross-authentication.   
We would like to see a unified approach of how to deal with CSRF, and since we we are discussing how to protect against clickjacking anyway, it would be the perfect time to protect against other forms too. We might very well see new forms of CSRF in the future (with offline storage, widgets or any other new technology), the solution should be extendable to combat other forms, current and future.

One proposed way of doing this would be a single header, of the form:
x-cross-domain-options: deny=frame,post,auth; AllowSameOrigin; allow=*.opera.com,example.net;
This incorporates the idea from the IE team, and extends on it. The header can be set centrally on a server, and content authors do not need to worry about checking their forms for CSRF, nor their scripts for the Origin header. The hope would be that future web servers would set the header by default, and that browsers, some time down the line, can treat a missing header as "deny=all". It also allows for easy extension should other types of CSRF be found in the future. 
Full compatibility with the proposal from the IE team would be maintained:
"x-cross-domain-options: deny=frame; AllowSameOrigin;" would be equivalent to "x-frame-options: SameOrigin"
"x-cross-domain-options: deny=frame;" would be equivalent to "x-frame-options: Deny"

Details:
The header takes three arguments:
* Deny - required - a comma separated list of CSRF that is disallowed. Current values are frame (clickjacking/cross-framing), post (cross-posting) and auth (cross-authentication).
* * If frame is set, the resource cannot be framed in (i)frames or object/embed/applet. For browsers that allow scripting in other objects, for example SVG images, the resource cannot be included in such elements either.
* * If post is set, only GET method will be allowed.
* * If auth is set, authentication tokens such as cookies or HTTP authentication should not be sent with the request.
* * There might be different options for the various deny values, this can be solved by sending multiple headers with different deny options. If several headers contain the same value, a browser should only heed the first header containing it. 
* * The list may be extended in the future
* Allow-same-origin - optional - a simple way to allow the same origin, without needing to hardcode the server address. Only applicable for the frame value.
* Allow - optional - a comma separated list of domain names, each settable with an initial "*." to cover any subdomains. Domains in this list are exempted to the CSRF rules specified in the deny option.

It would be possible to add "all" and "none" to the list of values for the deny argument. "All" would be useful as a default, and protect against future CSRF attacks, although such future additions in browsers might break existing services. "None" would be useful for simple debugging, and if a web server wants to reserve itself against future changes in web browsers.

For cross-domain resources, this means that a browser would first have to make a request with GET and without authentication tokens to get the x-cross-domain-options settings from the resource. If the settings allow, a second request may be made, if the second request would be different. The result of last request are handed over to the document.

Note that this would mean that some third party resources would get two requests where they only get one today. If such a GET request has side effects without authentication, and the request would have been made with a different method or with authentication tokens in a legacy browser, the server might register two hits instead of one after this change in browsers. This might affect for instance the second time a user visists a web site using a third party counter, where the counter sets a cookie but does not bother about checking the cookies. Servers get non-authenticated GETs from spiders all the time, so this should not be a widespread or serious problem. Pre-flight requests would not be required for same-origin.

Such pre-flight requests could be countered by a cacheable domain wide policy file which the browser looks for the first time a session uses cross-site resources from that domain. If set, the browser will use that setting for all requests, if not set, it will check individual resources with a pre-flight request. This would make it easy for legacy servers to become compatible. Note that W3Cs cross-site XHR has opted not to use domain wide policy files, that solution is not applicable to CSRF in general though, as it does not need to take legacy servers into account. To allow double requests, it would be possible to extend with a server header telling a browser that the information is the same whether the user is logged in or not, eliminating the need for a second, authenticated, request.

This is suggestion, please tear it apart and use the best pieces of it to build something better.


[1] http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2009-February/018586.html

-- 
Sigbj?rn Vik
Security Team
Opera Software

Received on Friday, 20 February 2009 06:46:08 UTC