2

According to https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token

The protection provided by this technique can be thwarted if the target website disables its same-origin policy using one of the following techniques:

Permissive Access-Control-Allow-Origin Cross-origin resource sharing header (with asterisk argument) ...

Is that correct? How does a Access-Control-Allow-Origin=* header thwart the protection? Cookie access is not governed by Access-Control-Allow-Origin headers. An attacker can never read that cookie's content via JavaScript if they are in a different origin, am I missing something?

Also if the value is * then withCredentials is automatically false (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials)

And even if it was possible, XHR doesn't allow you to read another domain's cookie, even if successfully set (https://fetch.spec.whatwg.org/#forbidden-header-name)

Is that claim wrong or perhaps I'm missing something? Or maybe the article meant that this in combination with the other items together is needed to thwart this method?

EDIT To clarify, the Cookie-to-header-token does not include a hidden input field, just a Cookie.

3 Answers 3

1

It can't. Wikipedia is just wrong here (and has been edited, as of this answer).

In fact, the cookie itself is utterly unnecessary in this scenario. On the server, don't look for any particular value in the X-Csrf-Token header, just make sure it is present. Then, don't return Access-Control-Allow-Headers: X-Csrf-Token (or ACAH: *) - at least, not for any domains you don't fully trust to remote-control your user sessions - and you're fully protected against CSRF. Any attempt to add the header to a cross-origin request will trigger a CORS preflight, and the preflight response won't allow the header, so the browser won't send the actual request. Simple as that. No need for a duplicated value in a cookie, no need for a random value, just make sure to not allow the header in CORS and you're done.


With that said, you do seem to have a little bit of confusion around CORS. You say

Also if the value is * then withCredentials is automatically false

That's not actually true, although it's kind of close. The browser does not - can not - know what the value of ACAO will be until it sends the request. Simple requests - including those with credentials (but not with custom headers) - are sent without any preflight. If my script tells the browser to send a "simple" CORS request with credentials, it'll do that.

Now, if the response comes back ACAO:*, the browser will NOT let my script see the response. It'll be as if the server hadn't returned ACAO at all, or had returned ACAO: MyDomain but hadn't also allowed credentials. However, the request will already have been sent! CSRF doesn't rely on or even expect being able to read the response - it's almost always a blind attack - and usually the server will act on it even if it was a "disallowed" request.

Non-simple CORS requests - those with custom headers, content types outside of the always-allowed ones, custom values for many standard headers (like Authorization), or methods (verbs) outside of the always-allowed ones - do trigger a preflight request. That preflight request generally will not take any action on the server (it has no body, no credentials, and uses the OPTIONS method) unless the server is very badly written indeed. Instead, the server is expected (if it supports CORS) to return a response telling the client (browser) what kinds of non-simple requests are allowed. The client checks this response against the actual request that the script wants to make, and only sends the actual request if it is allowed.

So, if the request is not simple, and the preflight response comes back ACAO:* but the request was supposed to include credentials, then the browser would just refuse to send the request at all (that's one place you'll get the error message in your link). The browser won't just omit the credentials and send the request anyway (which is implied by "then withCredentials is automatically false")! Alternatively, if it's a simple request, the browser will send the actual request (not a preflight!) first, and only check the ACAO and ACAC headers in that response, and potentially deny you access to the response details. The actual request will have been sent, though!

0

Usually a CSRF token is available in the body of a response from the server. They're commonly inside a hidden input inside a form in an html response. If that page includes a Access-Control-Allow-Origin=* header, then any website on the internet could make a request for that page and get the CSRF token.

4
  • Yes, but I'm talking about the Cookie-to-header token method, there is no hidden field, just a cookie.
    – Eran Medan
    Feb 20, 2019 at 23:46
  • Oh, right. I can't see how that warning applies to that strategy either. I assume that warning was copied from another section or article about general CSRF protections.
    – Macil
    Feb 20, 2019 at 23:49
  • Yeah, looks like a copy paste error
    – Eran Medan
    Feb 20, 2019 at 23:52
  • p.s. just tried it, (permissive cors, set a CSRF token that is session based, went to jsfiddle and did an XHR with credentials to that page, since it sends credentials, it sends my session cookie, thus shows the same page I see e.g., shows the hidden field with my token like a charm, important lesson on how critical is avoiding * CORS headers, as even if you are only serving JSON, one day you'll return an HTML page and forget... wow) this is surprising as I thought * blocks withCredentials according to: developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/…
    – Eran Medan
    Feb 21, 2019 at 0:15
0

An attacker can never read that cookie's content via JavaScript if they are in a different origin, am I missing something?

You might be missing something regarding HTTP cookies but it's probably not the main issue here, but it's important to fully understand the subtle aspects of cookie semantics because they are widely used for security sensitive purposes.

Cookies' "origin"

You can only read cookies by JS if they are associated to the page and sent to the server in the HTTP request: it's a necessary condition (but not sufficient obviously as cookies can be made "HTTP only"). So what you are saying is that an HTTP request to an URL with origin A only sends cookies set by pages from A. But this is not true.

Actually you very often get cookies from a different (but not arbitrary) origin:

  • http://x and https://x are different origins, but HTTPS URLs receive HTTP cookies (and there is no way to refuse that or even sort out cookies by actual origin); the common fix for that is to make a domain HTTPS only in the browser via prelisting or HSTS headers.

  • for any secure or insecure scheme, domain.register and sub.domain.register can share cookies: cookies set in domain are visible in the subdomain, the subdomain can choose to make some cookies visible at a wider domain, limited to "register", the domain where the browser believes people can register domain (which isn't even always a perfectly well defined concept); again there is no way for a server to determine who set which cookie, so unless you control all every related domain you might get additional cookies.

Not only that, the interaction of cookies of the same name was never well defined and browser independent and different browsers had different behavior.

Now by hypothesis an attacker cannot invent an arbitrary subdomain in your domain and make a secure connection to it work; if he could, it would be as easy to just intercept a secure connection to the main domain, as by hypothesis the attacker would either be able to get a certificate that he isn't allowed to get, or to make users dismiss security warnings. So if you set a secure cookie for domain, the attacker can't intercept it by playing with subdomains.

The purpose of permission/authentification cookies

Back to the original issue, which was never to protect a cookie value but the rights linked to an authenticated requested, linked with an arbitrary and meaningless value: a cookie as in the older X server authentication cookie is just a meaningless number that has no value in itself, only in connection with a server that attributes meaning to that cookie.

You are not just trying to keep a numerical value secret, you want to protect the functionality of that value. Say you have an electronic card containing a secret key that can't be copied, and that can only be used on a safe to open it; if the card can be stolen from you and used without your knowledge, the content of the safe isn't secure; although the secret code was always inside the card, and no attacker ever learns that code.

Protecting HTTP cookies that represent a permission on the server (or an authentification status, or anything that a random anonymous user on a website cannot do) means preventing the cookies from being misused even in ways that don't allow an attacker to learn a meaningless numeric value.

That's where "HTTP only" isn't magical and does not guarantee complete security if you allow arbitrary Javascript from the same origin as your Website managing private information.

[Note that the designers of the HTTP only flag explicitly warned against over reliance on it as a protection against JS injection - something that many people decided to ignore thinking that the "HTTP only" is only needed protection against XSS for sites that have valuable authentication cookies.]

A secret cookie protects access to a Web ressource and allowing arbitrary operations to be done by any external agent (running on an arbitrary domain) via a browser defeats the purpose of protecting that secret - even if the secret says secret, like an access card you would let anyone use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.