justinas / nosurf

CSRF protection middleware for Go.

Home Page:http://godoc.org/github.com/justinas/nosurf

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Signing Cookies

elithrar opened this issue · comments

nosurf does not currently sign cookies as the standard http.Cookie implementation only defines the "basic" attributes of a cookie.

CSRF cookies should be signed (so we can identify attempts to tamper) with HMAC-SHA256, and then authenticated before checking the cookie against the submitted request. An example of a solid authentication implementation can be found here.

  • Authenticated cookies should (really) really be the default, but I'm not sure how to reconcile this without breaking the existing API. I would argue that the benefit from authenticating cookie values outweighs the downside of breaking the API. Any major change would be a compile-time error too, and is therefore easier to resolve (no weird gremlins at run-time).
  • I would also consider updating the README to stress (as it's extremely important) that package users serve their site over HTTPS (SSL/TLS), as otherwise CSRF tokens are effectively lip service given that the cookies themselves can be hijacked (and as their contents aren't authenticated, changed at whim).
  • Encrypted cookies would be an additional "nice to have" but do not really circumvent the need for SSL/TLS. The existing "encryption" references in the docs should also be changed to "masked" or "masking".
  • You would facilitate authentication/encryption by allowing a user to pass in keys in a func (h *CSRFHandler) SetAuthKeys(key []byte, keys ...[]byte) and func (h *CSRFHandler) SetEncryptionKeys(key []byte, keys ...[]byte), with the variadic param allowing a package user to pass in multiple key pairs (which facilitates cycling keys). This would be similar to how gorilla/securecookie handles key rotation, but you could probably get away with just accepting a single key.
  • Leveraging gorilla/securecookie itself may not be a bad idea. You could wrap its exported functions with your own to maintain as much of your existing API as possible, or have nosurf.New accept an options struct that then calls securecookie API before then returning a configured *CSRFHandler.
  • I'd also suggest bringing the default expiry way down to something like a week, tops. Even a day would be fine—users don't take a day to fill out a form.

Not sure about this one. If the attacker is a MITM, there are attack vectors that are much more dangerous (and more straightforward to execute at the same time). Simply stealing the session cookie seems much more viable for the attacker than tampering with a CSRF cookie, tricking the user into visiting the hostile website, etc.

Just to add to it: other CSRF implementations (like Django or Flask-SeaSurf) do not seem to use any kind of signatures on the CSRF cookies.

Django's cookies are signed by default. Rails is the same, since 3.0 if I
recall correctly.

The primary benefit is that you can detect any attempt to tamper: because
even a MITM over vanilla HTTP can't get the key, any modified cookie
contents would automatically fail validation. This would hold true even if
they could modify the form and the cookie contents to match.

This also benefits HTTPS connections, which are still at risk from the
above when HTTP to HTTPS redirects are in place (instead of STS headers).

On Monday, July 28, 2014, Justinas Stankevičius notifications@github.com
wrote:

Just to add to it: other CSRF implementations (like Django or
Flask-SeaSurf) do not seem to use any kind of signatures on the CSRF
cookies.


Reply to this email directly or view it on GitHub
#11 (comment).

I should also mention that because the cookies are not HttpOnly by default, any XSS vulnerability (which does not require a MITM!) can write to the cookies directly and avoid the same-origin policy that would normally restrict that. I assume that we don't enforce HttpOnly for convenience—so that AJAX requests can get the CSRF token—but this opens up another avenue for attack.

We can comfortably (under any reasonable circumstance) assume that no-one is going to brute-force our HMAC-SHA256 signed values, so any attempts to modify the cookie via XSS (which is going to be the most common), MITM on our users' site or even a MITM on any external scripts we use—can at least be detected and the request rejected.

I don't think Django signs cookies by default. As I see it, there are both set_cookie() and set_signed_cookie() functions. CsrfViewMiddleware seems to use the former.

Your point about the XSS vulnerabilities is a valid and convincing one, though. I will think about how to integrate the signing some more, mostly whether to break API and make it default (forced?).

Ah, good catch. Django’s default cookie store is signed (https://docs.djangoproject.com/en/dev/topics/http/sessions/#using-cookie-based-sessions) but I didn’t realise this doesn’t include their CSRF implementation. Odd!

On 29 Jul 2014, at 4:58 AM, Justinas Stankevičius notifications@github.com wrote:

I don't think Django signs cookies by default. As I see it, there are both set_cookie() and set_signed_cookie() functions. CsrfViewMiddleware seems to use the former.

Your point about the XSS vulnerabilities is a valid and convincing one, though. I will think about how to integrate the signing some more, mostly whether to break API and make it default (forced?).


Reply to this email directly or view it on GitHub.

This only applies to the session cookie. All other cookies are up to user to sign.

Well this has been sitting open for a while.

Seeing how there are now alternatives like goji-csrf which do use cookie signing and the fact that it would change the API, I think this should be considered out of scope for now.