...And They Have a Plan
No notes are so disjointed as the ones skulking about my brain as I was preparing slides for last week’s BlackHat presentation. I’ve now wrangled them into a mostly coherent write-up.
This won’t be the last post on this topic. I’ll be doing two things over the next few weeks: throwing a doc into github to track changes/recommendations/etc., responding to more questions, working on a different presentation, and trying to stick to the original plan (i.e. two things). Oh, and getting better at MarkDown.
So, turn up some Jimi Hendrix, play some BSG in the background, and read on.
== The Problem ==
Cross-Site Request Forgery (CSRF) abuses the normal ability of browsers to make cross-origin requests by crafting a resource on one origin that causes a victim’s browser to make a request to another origin using the victim’s security context associated with that target origin.
The attacker creates and places a malicious resource on an origin unrelated to the target origin to which the victim’s browser will make a request. The malicious resource contains content that causes a browser to make a request to the unrelated target origin. That request contains parameters selected by the attacker to affect the victim’s security context with regard to the target origin.
The attacker does not need to violate the browser’s Same Origin Policy to generate the cross origin request. Nor does the attack require reading the response from the target origin. The victim’s browser automatically includes cookies associated with the target origin for which the forged request is being made. Thus, the attacker creates an action, the browser requests the action and the target web application performs the action under the context of the cookies it receives – the victim’s security context. An effective CSRF attack means the request modifies the victim’s context with regard to the web application in a way that’s favorable to the attacker. For example, a CSRF attack may change the victim’s password for the web application.
CSRF takes advantage of web applications that fail to enforce strong authorization of actions during a user’s session. The attack relies on the normal, expected behavior of web browsers to make cross-origin requests from resources they load on unrelated origins.
The browser’s Same Origin Policy prevents a resource in one origin to read the response from an unrelated origin. However, the attack only depends on the forged request being submitted to the target web app under the victim’s security context – it does not depend on receiving or seeing the target app’s response.
== The Proposed Solution ==
SOS is proposed an additional policy type of the Content Security Policy. Its behavior also includes pre-flight behavior as used by the Cross Origin Resource Sharing spec.
SOS isn’t just intended as a catchy an acronym. The name is intended to evoke the SOS of Morse code, which is both easy to transmit and easy to understand. If it is required to explain what SOS stands for, then “Session Origin Security” would be preferred. (However, “Simple Origin Security”, “Some Other Security”, and even “Save Our Site” are acceptable. “Same Old Stuff” is discouraged. More options are left to the reader.)
An SOS policy may be applied to one or more cookies for a web application on a per-cookie or collective basis. The policy controls whether the browser includes those cookies during cross-origin requests. (A cross-origin resource cannot access a cookie from another origin, but it may generate a request that causes the cookie to be included.)
== Format ==
A web application sets a policy by including a Content-Security-Policy response header. This header may accompany the response that includes the Set-Cookie header for the cookie to be covered, or it may be set on a separate resource.
A policy for a single cookie would be set as follows, with the cookieName of the cookie and a directive of 'any'
, 'self'
, or 'isolate'
. (Those directives will be defined shortly.)
Content-Security-Policy: sos-apply=_cookieName_ '_policy_'
A response may include multiple CSP headers, such as:
Content-Security-Policy: sos-apply=_cookieOne_ '_policy_'
Content-Security-Policy: sos-apply=_cookieTwo_ '_policy_'
A policy may be applied to all cookies by using a wildcard:
Content-Security-Policy: sos-apply=* '_policy_'
== Policies ==
One of three directives may be assigned to a policy. The directives affect the browser’s default handling of cookies for cross-origin requests to a cookie’s destination origin. The pre-flight concept will be described in the next section; it provides a mechanism for making exceptions to a policy on a per-resource basis.
Policies are only invoked for cross-origin requests. Same origin requests are unaffected.
'any'
– include the cookie. This represents how browsers currently work. Make a pre-flight request to the resource on the destination origin to check for an exception response.
'self'
– do not include the cookie. Make a pre-flight request to the resource on the destination origin to check for an exception response.
'isolate'
– never include the cookie. Do not make a pre-flight request to the resource because no exceptions are allowed.
== Pre-Flight ==
A browser that is going to make a cross-origin request that includes a cookie covered by a policy of 'any'
or 'self'
must make a pre-flight check to the destination resource before conducting the request. (A policy of 'isolate'
instructs the browser to never include the cookie during a cross-origin request.)
The purpose of a pre-flight request is to allow the destination origin to modify a policy on a per-resource basis. Thus, certain resources of a web app may allow or deny cookies from cross-origin requests despite the default policy.
The pre-flight request works identically to that for Cross Origin Resource Sharing, with the addition of an Access-Control-SOS
header. This header includes a space-delimited list of cookies that the browser might otherwise include for a cross-origin request, as follows:
Access-Control-SOS: cookieOne CookieTwo
A pre-flight request might look like the following, note that the Origin header is expected to be present as well:
OPTIONS https://web.site/resource HTTP/1.1
Host: web.site
Origin: https://other.origin
Access-Control-SOS: sid
Connection: keep-alive
Content-Length: 0
The destination origin may respond with an Access-Control-SOS-Reply
header that instructs the browser whether to include the cookie(s). The response will either be 'allow'
or 'deny'
.
The response header may also include an expiration in seconds. The expiration allows the browser to remember this response and forego subsequent pre-flight checks for the duration of the value.
The following example would allow the browser to include a cookie with a cross-origin request to the destination origin even if the cookie’s policy had been 'self
’. (In the absence of a reply header, the browser would not include the cookie.)
Access-Control-SOS-Reply: 'allow' expires=600
The following example would deny the browser to include a cookie with a cross-origin request to the destination origin even if the cookie’s policy had been 'any'
. (In the absence of a reply header, the browser would include the cookie.)
Access-Control-SOS-Reply: 'deny' expires=0
The browser would be expected to track policies and policy exceptions based on destination origins. It would not be expected to track pairs of origins (e.g. different cross-origins to the destination) since such a mapping could easily become cumbersome, inefficient, and more prone to abuse or mistakes.
As described in this section, the pre-flight is an all-or-nothing affair. If multiple cookies are listed in the Access-Control-SOS
header, then the response applies to all of them. This might not provide enough flexibility. On the other hand, simplicity tends to encourage security.
== Benefits ==
Note that a policy can be applied on a per-cookie basis. If a policy-covered cookie is disallowed, any non-covered cookies for the destination origin may still be included. Think of a non-covered cookie as an unadorned or “naked” cookie – their behavior and that of the browser matches the web of today.
The intention of a policy is to control cookies associated with a user’s security context for the destination origin. For example, it would be a good idea to apply 'self'
to a cookie used for authorization (and identification, depending on how tightly coupled those concepts are by the app’s reliance on the cookie).
Imagine a Wordpress installation hosted at https://web.site/. The site’s owner wishes to allow anyone to visit, especially when linked-in from search engines, social media, and other sites of different origins. In this case, they may define a policy of 'any'
set by the landing page:
Content-Security-Policy: sos-apply=sid 'any'
However, the /wp-admin/ directory represents sensitive functions that should only be accessed by intention of the user. Wordpress provides a robust nonce-based anti-CSRF token. Unfortunately, many plugins forget to include these nonces and therefore become vulnerable to attack. Since the site owner has set a policy for the sid cookie (which represents the session ID), they could respond to any pre-flight request to the /wp-admin/ directory as follows:
Access-Control-SOS-Reply: 'deny' expires=86400
Thus, the /wp-admin/ directory would be protected from CSRF exploits because a browser would not include the sid cookie with a forged request.
The use case for the 'isolate'
policy is straight-forward: the site does not expect any cross-origin requests to include cookies related to authentication or authorization. A bank or web-based email might desire this behavior. The intention of isolate is to avoid the requirement for a pre-flight request and to forbid exceptions to the policy.
== Notes ==
This is a draft. The following thoughts represent some areas that require more consideration or that convey some of the motivations behind this proposal.
This is intended to affect cross-origin requests made by a browser.
It is not intended to counter same-origin attacks such as HTML injection (XSS) or intermediation attacks such as sniffing. Attempting to solve multiple problems with this policy leads to folly.
CSRF evokes two sense of the word “forgery”: creation and counterfeiting. This approach doesn’t inhibit the creation of cross-origin requests (although something like “non-simple” XHR requests and CORS would). Nor does it inhibit the counterfeiting of requests, such as making it difficult for an attacker to guess values. It defeats CSRF by blocking a cookie that represents the user’s security context from being included in a cross-origin request the user likely didn’t intend to make.
There may be a reason to remove a policy from a cookie, in which case a CSP header could use something like an sos-remove instruction:
Content-Security-Policy: sos-remove=cookieName
Cryptographic constructs are avoided on purpose. Even if designed well, they are prone to implementation error. They must also be tracked and verified by the app, which exposes more chances for error and induces more overhead. Relying on nonces increases the difficulty of forging (as in counterfeiting) requests, whereas this proposed policy defines a clear binary of inclusion/exclusion for a cookie. A cookie will or will not be included vs. a nonce might or might not be predicted.
PRNG values are avoided on purpose, for the same reasons as cryptographic nonces. It’s worth noting that misunderstanding the difference between a random value and a cryptographically secure PRNG (which a CSRF token should favor) is another point against a PRNG-based control.
A CSP header was chosen in favor of decorating the cookie with new attributes because cookies are already ugly, clunky, and (somewhat) broken enough. Plus, the underlying goal is to protect a session or security context associated with a user. As such, there might be reason to extended this concept to the instantiation of Web Storage objects, e.g. forbid them in mixed-origin resources. However, this hasn’t really been thought through and probably adds more complexity without solving an actual problem.
The pre-flight request/response shouldn’t be a source of information leakage about cookies used by the app. At least, it shouldn’t provide more information than might be trivially obtained through other techniques.
It’s not clear what an ideal design pattern would be for deploying SOS headers. A policy could accompany each Set-Cookie header. Or the site could use a redirect or similar bottleneck to set policies from a single resource.
It would be much easier to retrofit these headers on a legacy app by using a Web App Firewall than it would be trying to modify code to include nonces everywhere.
It would be (possibly) easier to audit a site’s protection based on implementing the headers via mod_rewrite
tricks or WAF rules that apply to whole groups of resources than it would for a code audit of each form and action.
The language here tilts (too much) towards formality, but the terms and usage haven’t been vetted yet to adhere to those in HTML, CSP and CORS. The goal right now is clarity of explanation; pedantry can wait.
== Cautions ==
In addition to the previous notes, these are highlighted as particular concerns.
Conflicting policies would cause confusion. For example, two different resources separately define an 'any'
and 'self'
for the same cookie. It would be necessary to determine which receives priority.
Cookies have the unfortunate property that they can belong to multiple origins (i.e. sub-domains). Hence, some apps might incur additional overhead of pre-flight requests or complexity in trying to distinguish cross-origin of unrelated domains and cross-origin of sub-domains.
Apps that rely on “Return To” URL parameters might not be fixed if the return URL has the CSRF exploit and the browser is now redirecting from the same origin. Maybe. This needs some investigation.
There’s no migration for old browsers: You’re secure (using a supporting browser and an adopted site) or you’re not. On the other hand, an old browser is an insecure browser anyway – browser exploits are more threatening than CSRF for many, many cases.
There’s something else I forgot to mention that I’m sure I’ll remember tomorrow.
I’ll leave you with this quote from the last episode of BSG. Thanks for reading!
Six: All of this has happened before.
Baltar: But the question remains, does all of this have to happen again?