Conversation
|
it would be cool to have an integration test that proves that it works and protects against regressions |
|
I was speaking with @kxepal and agreed to post the code so we could figure out how to test it. |
src/couch_httpd.erl
Outdated
There was a problem hiding this comment.
Can we have CSRF cookie name configurable to avoid possible collisions with other web services that also defined CSRF protection? I cannot name any that uses the same "Csrf-token" value (Django have "csrftoken", Pyramid - "csrf_token"), but collision probability rate is still could be high here.
Or may be use "CouchDB-CSRF-Token" name by default - unlikely it will cause any problems.
There was a problem hiding this comment.
CouchDB-CSRF and X-CouchDB-CSRF sound fine to me.
src/couch_httpd_auth.erl
Outdated
There was a problem hiding this comment.
mmm...so we here
- Enable CSRF protection only by user request, not for the whole server. What means, that it will be easy to forget to activate that protection.
- CSRF is not applied for Basic and OAuth authed users and those that authed using other methods as they don't POST /_session. They could only GET it, but it's not mandatory.
What are your thoughts about these cases?
There was a problem hiding this comment.
If we don't make it optional then every client will be broken until they add this custom header.
On basic auth, I tend to think of cookie auth being used by browsers/ui's and basic auth being used by programs. CSRF is only an issue for browsers; direct api interactions aren't vulnerable.
There was a problem hiding this comment.
I meant https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-Header_Token earlier, sorry for confusion.
|
if we now understand the general mechanism, my remaining questions are;
The core parts are sound, I think. The generation of the csrf cookie and the way we validate that the cookie and header match and are valid. What's not quite right is the way you get the csrf cookie in the first place. Thoughts? |
|
I've revised this and removed the coupling with the session cookie. A client that does not currently have a CouchDB-CSRF cookie can add X-CouchDB-CSRF: true to their request and will receive one. They must then send the cookie's value in the X-CouchDB-CSRF header for their request to succeed. The CSRF token check occurs before authentication so you can acquire the CSRF token immediately (perhaps when fetching the welcome message from /). The token will expire from time to time (same duration as the session cookie but without the automatic extension), so clients should do broadly this; if (hasCookie("CouchDB-CSRF")) { |
a5cbc6e to
64f08ea
Compare
|
I like new logic much more. Good improvements! However, your questions are still valid: if CSRF cookie expires, how it will get prolonged? Especially, since auth cookie renews automagicaly when it ttl comes to the end. |
|
Because the client will be testing if they have a CSRF cookie (in order to know whether to set the CSRF header). It will vanish, so they will set X-CouchDB-CSRF:true on the next request to get a new one. |
|
I see. This could work. |
|
hm, I'm thinking of adding automatic refresh, like we do for session cookie, to prevent even that little gap where there's no CSRF token at expiration. |
|
I think such refresh could be useful and making CSRF tokens expire is even better. |
|
So I've added the refresh.When a cookie is more than halfway through its lifetime (which defaults to an hour) a replacement cookie is sent. Additionally, if you send the CSRF header but not the cookie, that's a 403 Forbidden. This helps clients stay safe (they won't think that just sending the header and getting a non-error response means that they are protected by the CSRF mechanism). |
|
The list of tests we need;
|
src/couch_httpd_csrf.erl
Outdated
There was a problem hiding this comment.
How about to catch a case if we receive malformed cookie here? While HTTP 500 because of badmatch or badarg is Erlang way, but more user friendly will be to be HTTP 400 here.
f92c0df to
1a9e0ea
Compare
|
Another small enhancement, if the cookie is invalid (corrupt or if the secret value was changed) then we clear the cookie, allowing the client to recover without waiting for expiration (which might be 59 minutes away!) |
004b31b to
17eb43d
Compare
src/couch_httpd_csrf.erl
Outdated
There was a problem hiding this comment.
You do decode_cookie/1 in validate/1 above where you handle invalid result, but here you don't. Sure, it's not logically possible to get badmatch here, but may be move check expiration to validate function to avoid decoding same cookie twice?
|
Sorry for double comment posting. GH eventually put my comments to your fork commit, not PR. Not sure how this happens. Also, check code for tabs: you have space-tabs mix a bit. |
|
turns out a lot of kits don't delete cookie if you set an expiration date in the past, so instead we send a fresh cookie if we get sent an invalid one. |
src/couch_httpd_csrf.erl
Outdated
There was a problem hiding this comment.
Is there a case for this branch? validate/1 calls in handle_request_int which will throw bad_request if Csrf is invalid while I see headers/1 is only called after it when response gets prepared where Csrf may be undefined or valid cookie as otherwise it will not pass validation. Or I miss something?
b0c003e to
946df14
Compare
|
Ok, I've stopped editing now, was just tidying a few things up. |
|
Perfect! Like latest changes about malformed and config section change. Have only notes about styling is you don't mind. |
src/couch_httpd_csrf.erl
Outdated
There was a problem hiding this comment.
It's the only place where header and cookie words not capitalized.
2b7511c to
5f7d56a
Compare
If the request parameter `csrf` is set to `true` when successfully acquiring a session cookie from `_session` an additional cookie (`Csrf-token`) is returned. All requests that send this new cookie must also send a header (`X-Csrf-Token`) with the same value. If the cookie is sent and the header is missing or different, a 403 response is generated. Note that the CSRF token is signed by the server so tampering is detected and also results in a 403 response. closes COUCHDB-2762
|
@kxepal +1? |
|
@rnewson +1! |
If the request parameter
csrfis set totruewhen successfullyacquiring a session cookie from
_sessionan additional cookie(
Csrf-token) is returned. All requests that send this new cookiemust also send a header (
X-Csrf-Token) with the same value. If thecookie is sent and the header is missing or different, a 403 response
is generated.
Note that the CSRF token is signed by the server so tampering is
detected and also results in a 403 response.
closes COUCHDB-2762