Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSRF failures due to duplicate XSRF-TOKEN values #1247

Closed
bitemyapp opened this issue Jul 8, 2016 · 8 comments
Closed

CSRF failures due to duplicate XSRF-TOKEN values #1247

bitemyapp opened this issue Jul 8, 2016 · 8 comments
Assignees

Comments

@bitemyapp
Copy link
Contributor

bitemyapp commented Jul 8, 2016

screenshot from 2016-07-08 13-09-39

Presently, what you see in the screenshot will work because the XSRF-TOKEN values are the same, however if they differ, you'll get that CSRF token middleware error we all know and love.

I do not know how two different XSRF tokens were generated (no repro), but it's not clear to me why there isn't a single XSRF token for the / path which covers requests being made from other pages anyway.

I read this thread: #1016

It did not clear up for me why there should be multiple X-XSRF-TOKEN values in the cookies.

AFAICT, this code is responsible for the duplicates:

addHeaderInternal :: MonadHandler m => Header -> m ()
addHeaderInternal = tell . Endo . (:)

Some snippets:

addHeader :: MonadHandler m => Text -> Text -> m ()
addHeader a = addHeaderInternal . Header (encodeUtf8 a) . encodeUtf8

setCookie :: MonadHandler m => SetCookie -> m ()
setCookie = addHeaderInternal . AddCookie

-- | Internal use only, not to be confused with 'setHeader'.
addHeaderInternal :: MonadHandler m => Header -> m ()
addHeaderInternal = tell . Endo . (:)

tell :: MonadHandler m => Endo [Header] -> m ()
tell hs = modify $ \g -> g { ghsHeaders = ghsHeaders g `mappend` hs }

data GHState = GHState
    { ghsSession :: SessionMap
    , ghsRBC     :: Maybe RequestBodyContents
    , ghsIdent   :: Int
    , ghsCache   :: TypeMap
    , ghsCacheBy :: KeyedTypeMap
    , ghsHeaders :: Endo [Header]
    }
@bitemyapp bitemyapp changed the title CSRF failures due to duplicate X-XSRF-TOKEN values CSRF failures due to duplicate XSRF-TOKEN values Jul 8, 2016
@bitemyapp
Copy link
Contributor Author

This prevents duplicates but the paths do change as you shuffle about. I don't know how acceptable a solution it is but it should at least prevent the CSRF failures we had.

https://github.com/bitemyapp/yesod/commit/5e4cefc9ad3880258d9685849dea1f9ea0daf768

@MaxGabriel
Copy link
Member

Oh, I didn't think about specifying the path when I implemented this. I haven't researched it yet, but

a single XSRF token for the / path which covers requests being made from other pages...

sounds good to me

@bitemyapp
Copy link
Contributor Author

bitemyapp commented Jul 8, 2016

@MaxGabriel my solution doesn't currently do that, but I could modify it. I don't really understand what place duplicate cookie values or mostly duplicate ones with different paths are even supposed to mean here. I'd like to understand the problem a bit more first before doing more.

@MaxGabriel
Copy link
Member

Here's my guess of what's going on:

  1. When the cookie is set by the middleware, its path defaults to the current path of the request.

  2. Based on a StackOverflow answer, cookies are uniqued by (domain, name, path), so there can be multiple cookies with the same name I guess. This is the relevant part of RFC 2965:

    3.3.3 Cookie Management If a user agent receives a Set-Cookie2
    response header whose NAME is the same as that of a cookie it has
    previously stored, the new cookie supersedes the old when: the old
    and new Domain attribute values compare equal, using a case-
    insensitive string-compare; and, the old and new Path attribute
    values string-compare equal (case-sensitive).

  3. When the Javascript looks up the cookie by name, it's probably getting the older cookie with the incorrect CSRF token value. It then sends the wrong CSRF token to the server, which the server rejects.

@MaxGabriel
Copy link
Member

Hm, I'm having trouble reproing getting the CSRF token for a path like "/signup", not sure why.

@bitemyapp
Copy link
Contributor Author

@MaxGabriel We have GHCJS code talking to the backend, did you try performing a CSRF secured AJAX call?

@MaxGabriel
Copy link
Member

Yes, using the comment AJAX text field that comes with the scaffolding. I can see the XSRF token in the headers of that POST request, too.

@MaxGabriel
Copy link
Member

Ok got it, wasn't thinking that the path needed to be something like:

localhost:3000/foo/bar

for the cookie path to be foo. Working on a fix

MaxGabriel added a commit that referenced this issue Jul 11, 2016
* The default path of cookies is the current path making the request
  * e.g. an AJAX request made from http://example.com/foo/bar would be /foo
  * This causes multiple CSRF tokens to build up as you navigate a site
  * This will cause errors if the CSRF tokens have different values, and an invalid token is sent.
* Closes #1247
MaxGabriel added a commit that referenced this issue Jul 11, 2016
* The default path of cookies is the current path making the request
  * e.g. an AJAX request made from http://example.com/foo/bar would be /foo
  * This causes multiple CSRF tokens to build up as you navigate a site
  * This will cause errors if the CSRF tokens have different values, and an invalid token is sent.
* Closes #1247
MaxGabriel added a commit to MaxGabriel/yesod that referenced this issue Aug 16, 2016
* The default path of cookies is the current path making the request
  * e.g. an AJAX request made from http://example.com/foo/bar would be /foo
  * This causes multiple CSRF tokens to build up as you navigate a site
  * This will cause errors if the CSRF tokens have different values, and an invalid token is sent.
* Closes yesodweb#1247
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants