90

We were recently handed a security report containing the following:

Cookie(s) without HttpOnly flag set

vulnerability, which we apparently had in one of our internal applications.

The applied fix was as simple as setting Django's CSRF_COOKIE_HTTPONLY configuration parameter to True.

But, this is what got me confused. The Django documentation says:

Designating the CSRF cookie as HttpOnly doesn’t offer any practical protection because CSRF is only to protect against cross-domain attacks. If an attacker can read the cookie via JavaScript, they’re already on the same domain as far as the browser knows, so they can do anything they like anyway. (XSS is a much bigger hole than CSRF.)

Although the setting offers little practical benefit, it’s sometimes required by security auditors.

Does this mean that there is no actual vulnerability here and we just have to be compliant with the security auditing rules?

7 Answers 7

98
+50

As joe says, there is no real security benefit to this. It is pure security theater. I'd like to highlight this from the documentation:

If you enable this and need to send the value of the CSRF token with an AJAX request, your JavaScript must pull the value from a hidden CSRF token form input on the page instead of from the cookie.

The purpose of the HttpOnly flag is to make the value of the cookie unavailable from JavaScript, so that it can not be stolen if there is a XSS vulnerability. But the CSRF-token must somehow be available so it can be double submitted - thats the whole point with it, after all. So Django solves this by including the value in a hidden form field. This negates the whole benefit of HttpOnly, since an attacker can just read the value of the form field instead of the cookie.

1
  • 24
    To rephrase and summarise your first two sentence slightly, for emphasis: the purpose of the HttpOnly flag is to make the value of the cookie unavailable from your site's JavaScript, but in an Ajax-powered webapp the entire function of a CSRF token relies upon it being available from your site's JavaScript. That's what makes the security report here so wrongheaded.
    – Mark Amery
    Commented Dec 15, 2017 at 10:29
18

I think the main point of confusion here is that the Django docs are specifically talking about the CSRF use case for a cookie. In order to understand why the httpOnly flag adds no value in preventing CSRF, you need to understand both CSRF and how cookies work.

CSRF is when a 3rd party triggers your user's browser to make a request to your server, and their browser automatically sends your server's cookies along with the request, as expected. What you don't want is for your server to interpret this request as actually coming from your user, so you use a CSRF mitigation technique. The whole point of CSRF mitigation is to be able to detect when the request didn't come from your own domain (i.e. from your user interacting with your application).

Briefly, how cookies work: Whenever a user's browser sends a request to your domain/server, it automatically sends all the cookies associated with your domain, regardless of the httpOnly flag. Cookies therefore allow your client or your server to attach information to a user's browser that will be returned to your server automatically along with any follow-on requests. Cookies with the httpOnly flag cannot be accessed from javascript. They probably shouldn't be considered a secure place to store information, but they do provide the advertised functionality.

Back to CSRF implemented using a cookie — in this case the httpOnly flag is pointless — the crux of CSRF is that they don't need to read your user's cookies, they just need your user's browser to send the associated cookies to your server along with the network request they forced it to send. The httpOnly flag, in general, does provide value in that it prevents client access to those cookies, and if your server returns any cookies, you should probably make them httpOnly. If you are using a cookie for CSRF, then, you shouldn't do that, and you should spend your time rethinking that rather than making it an httpOnly cookie. So, in general, it seems like that is a good rule of thumb — your server shouldn't send any non-httpOnly cookies unless it is specifically intended to be accessed by the client javascript.

0
14

That is correct. This is a false positive and the person providing this finding to you does not understand what they are doing unfortunately. Someone that understood the risks of mitm and csrf attacks would never provide this to you.

5
  • 16
    While this argument is true in this particular case it is recommended to employ strict security settings by default (which includes using httponly) and only have less strict settings if this is really needed by the application and only if one is really sure that loosening security setting does not cause harm in the specific case. In other words: unless there is a reason the CSRF cookie can not be httponly it should be httponly too. Thus, I would actually agree with the security report because not having secure defaults whenever possible is a bad idea. Commented Dec 15, 2017 at 5:05
  • 2
    @SteffenUllrich How could a double submit token work if it is not available from JS? The Django option in question does more than just setting a flag. See my answer.
    – Anders
    Commented Dec 15, 2017 at 5:22
  • 3
    @Anders: the statement you quote in your nice answer shows a different picture than the question and this answer. And, it does not contradict my comment which said "... unless there is a reason the CSRF cookie can not be httponly it should be httponly too". Obviously in this case there is a reason it should not be httponly and you've showed it in your answer. Commented Dec 15, 2017 at 5:36
  • 5
    @SteffenUllrich While I agree with your sentiment in general, encouraging people to reason about complex security themselves ("unless there is a reason the CSRF cookie can not be httponly it should be httponly too") may be worse than clearly stating "Yeah, CSRF cookies are an exception, you can make them httponly because what they are doing does not need httponly to be secure.". It encourages people to apply reasoning to other areas where the case isn’t as clear and where they might end up with the wrong (insecure) result. Commented Dec 15, 2017 at 8:34
  • 1
    @joe yes they would as you pay for a full report and generally that flag is security issue. Unless you say that this is 'out of scope' I would expect that to come back in a report. Don't expect pentesters to understand 100% of the environment you work in or frameworks you choose - its a vulnerability, its up to YOU to figure out the business risk
    – McMatty
    Commented Nov 29, 2018 at 23:51
7
+100

The only way CSRF prevention with double-submitting can work is by sending the nonce in a cookie. If you send it in the HTTP response body, it can in some cases be parsed out by a script sending a cross-domain request, (if you've allowed CORS for that page) which defeats the whole purpose of protecting against CSRF. The idea is that scripts on domain X can’t get the value of cookies on domain Y, this is one f the main pillars of security on the Web.

With HTTPOnly cookies you won’t be able to protect against CSRF using the most common method - namely double-submitting a nonce via cookies and a script on the page able to read the cookies. Here is how that technique works:

1) Generate a nonce value that you store on the server for the session. It could be the session ID itself, or something stored in the data associated to the session.

2) Send this to the client via the cookie headers without HTTPOnly, have some Javascript grab it and store it (e.g. short term or in sessionStorage)

3) Submit this with every request that you want to protect from CSRF. The request would have the sessionId in the cookie, and also this nonce in the request body or into the URL querystring (e.g. for GET requests). If you are worried about the nonce winding up in logs or whatever, you can instead use Web Crypto to sign request payloads with HMAC using this nonce

4) The server will look up the session data, and check if the nonce doesn't match, throws an error and generates another nonce and sends in the cookie. Make sure to send the nonces only in cookies!

It is unfortunate but true that many “security audit” tools start to flag cookies not marked with httpOnly and recommend that this flag be added. This flag is sadly worse than useless: it is security theater.

I'm talking only about the httpOnly flag. It's worse than useless because it makes you think that you dodged some kind of bullet, when in fact that same class of attack can still happen: the attacker needs to craft a script to send the cookie to their server, so they might as well have that script execute the actual requests in the context of your authorized session, the same way that they'd do it if they had your cookie. They would write the script ahead of time. No real attacker would sit there at 3 AM making requests because their victim finally activated a script that sent them the cookie. Far more likely, that script is already pre-programmed to do what it needs to do. And if httpOnly cookies didn't exist, the people would care more about sanitizing their Javascript output to prevent XSS, which is the only correct way to prevent this attack.

8
  • 1
    "If you send it in the HTTP response body, it can be parsed out by a script sending a cross-domain request..." This is FALSE unless you have severely screwed up your CORS configuration, a very serious vuln in its own right. SOP prevents cross-origin requests from being read by the originating script. This approach provides some defense-in-depth against such a catastrophic misconfiguration of CORS, but the marginal security gain from such DiD is much less than you'd get by using something more secure than double-submit cookies for CSRF protection, which is relatively easy to bypass.
    – CBHacking
    Commented Dec 3, 2019 at 20:42
  • "With HTTPOnly cookies you won’t be able to protect against CSRF using the most common method - namely double-submitting a nonce via cookies and a script on the page able to read the cookies." Can you provide more background on this technique? "...to prevent XSS, which is the only correct way to prevent this attack." I believe XSS concerns are orthogonal to CSRF concerns.
    – colllin
    Commented Dec 4, 2019 at 1:24
  • @CBHacking thank you, I have updated my answer to make it clearer that the vulnerability should only appear if you mishandle CORS or fail to use HTTPS. Commented Dec 4, 2019 at 3:05
  • @collin does that help? Commented Dec 4, 2019 at 3:15
  • 1
    @GregoryMagarshak HMAC of the session token (which is already a de-facto nonce) or some other unpredictable value tied to the user (and ideally the session). This prevents cookie planting attacks, header-injection (for adding a cookie) attacks, session fixation-type attacks, and at least partially avoids the risk of using a bad PRNG for the token (although then there's the question of how secure your session tokens are). It doesn't require extra server-side state, can be done either with or without using cookies, and doesn't expose the session token even if the CSRF token is exposed.
    – CBHacking
    Commented Dec 4, 2019 at 3:40
3

While the CSRF-Token is no secret on a page with a CSRF protected form (and could be stolen using XSS), you may have a XSS vulnerability in some page without a form. There you can only get the token from the cookie but not from a hidden field. In this case the security is improved by using a HttpOnly cookie.

There is still a way to get the token by requesting a page which contains a form via javascript. There may be ways to prevent this, but on most sites this attack will be possible.

4
  • 5
    If there is a XSS vulnerability on a paget without a form I can just request a page with a form and read the token (a little extra work, but not a lot). And if there is an XSS vulnerability, why would I need CSRF anyway? I can just do what I want.
    – Anders
    Commented Dec 22, 2017 at 14:23
  • 1
    That's a point. To your question: CSRF protection is a way to make XSS less dangerous. If you inject some script which tries to submit a POST request, it needs to get the CSRF token from somewhere. As you said, it is quite possible to try to extract it by loading another page which contains a form.
    – allo
    Commented Dec 22, 2017 at 14:27
  • 2
    @allo No, CSRF protection does not make XSS even slightly less dangerous and it is quite irresponsible to claim so! The necessity of using XSS-injected script to either make a same-origin GET request to any page with a CSRF form token or just set the cookie yourself using JS (assuming it's not authenticated to the session in any way, which it usually isn't) is nothing but an utterly trivial speedbump. Pretending this will make you any secure is simply wishful thinking, and any competent attacker would route around this "protection" without even noticing it was there.
    – CBHacking
    Commented Dec 3, 2019 at 20:36
  • 1
    I addressed the question, what advantages HttpOnly has, not if it eliminates XSS vulnerabilities. In the comment above yours I clarified and described what can still be used to circumvent the additional protection, that HttpOnly provides. I see your point, but my post and comment already contain the limits of the security that this setting is able to provide. I think you should add your points as an additional answer to the question, because you're more addressing the question than my answer and you're points are enough to write an answer.
    – allo
    Commented Dec 4, 2019 at 15:04
3

Designating the CSRF cookie as HttpOnly doesn’t offer any practical protection because CSRF is only to protect against cross-domain attacks.

This can be stipulated in a much more general way, and in a simpler way by remove the technical aspect of "CSRF cookie".

Designating a cookie as HttpOnly, by definition, only protects against access via document.cookie or equivalent JS methods. It doesn't prevent any HTTP interaction that may have been caused by JS code; any interaction that the user does via HTML elements, like a form submission, can be started by JS. There is no meaningful distinction of how something was started; in fact, you can claim "the user started it" by pointing to the user typing it the URL of the current Webpage, or that of some Webpage that linked to the current Webpage.

That is also the reason why the concept of "autoplay of video" isn't well defined, and cannot be prevented reliably: what constitutes a voluntary user action to start a video is a user interface concept, not a DOM (Document Object Model) concept. The browser doesn't know autoplay from user made a gesture to play a video, unless the video starts before the user makes any move (such as "space to scroll down"). (One can try to "wack a mole" autoplay in a few cases, like one can try to "wack a mole" (detect, black-list) annoying Web ads, or sharing information with third party domains, but without guarantee of coverage.)

Unless JS is completely turned off on a domain, any user action inside the frame controlled by the Website should simply be assumed to be doable by script of the Website. One the modern Web, who turns off JS completely on most domains? Almost no one.

So the generalized observation is:

Designating any cookie as HttpOnly doesn’t offer any practical protection against any against attacks that perform actions that the user might perform through the interface of the Website.

Note that reading all cookies (including those marked HttpOnly) is not done by the interface of the website, it can be done only with the Inspector tool of the browser, or the HTTP proxy for cookies sent over HTTP.

2

The httpOnly attribute can be omitted from the anti-CSRF cookie in Django because, as many other answers and even the question highlighted, javascript code running in victim browsers that loaded an unauthorized origin will not be able to read authenticated responses from the authorized origin, with or without the attribute, due to Same Origin Policy.

(Let's not mix the strict requirement for the session cookie with the one for the anti-CSRF cookie).

Django expects developers to fill the HTML form with the anti-CSRF token related (or same) with the XSRF-TOKEN cookie.

https://github.com/django/django/blob/f283ffaa84ef0a558eb466b8fc3fae7e6fbb547c/django/middleware/csrf.py#L136

The Django authors are careful enough to stipulate the need for the Secure attribute (which applies to both the session cookie and the anti-CSRF one). They might as well mention the need to add the Strict-Transport-Security response header to avoid cookie injection and/or eavesdropping of the entire interaction by letting the browsers slip into the http:// link and by rewriting the unencrypted traffic.

The framework authors are not strict about the httpOnly attribute because, thanks to Same Origin Policy, unauthorized origins would not be able to read cookies via the javascript code they generate for the browsers; their server-side scripts trying to get the form details and response headers from the authorized origin will receive only unauthenticated responses.

I just stumbled on a hard requirement of NOT allowing httpOnly for a XSRF-TOKEN cookie when it is used as the only source for Single Page Applications. SPAs relying on the XSRF-TOKEN response cookie are still safe. Unauthorized origins, again, will not be able to read the proper XSRF-TOKEN response cookie due to Same Origin Policy. And the unauthorized server-side scripts would not be able to pre-fetch the proper XSRF-TOKEN response cookie either (again, because the scripts lack authentication with the authorized server).

https://github.com/expressjs/csurf#single-page-application-spa

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .