1. Luke Plant
  2. ella

Commits

Luke Plant  committed 6f1bef1

Only send CSRF cookie when token is in outgoing response.

  • Participants
  • Parent commits 9394e79
  • Branches default

Comments (0)

Files changed (3)

File ella.cabal

View file
  • Ignore whitespace
   Build-Depends:
         base == 4.*,
         bytestring >= 0.9.1,
+        stringsearch >= 0.2.1.1,
         haskell98 >= 1.0.1,
         containers >= 0.1.0,
         utf8-string >= 0.3.1,

File src/Ella/Processors/Security.hs

View file
  • Ignore whitespace
 where
 
 import Control.Monad (guard)
+import Data.ByteString.Search.KnuthMorrisPratt (matchLL)
 import Data.Digest.Pure.SHA (showDigest, sha1)
 import Data.Maybe (isJust, fromJust, isNothing)
 import Ella.Framework
                              Nothing -> return Nothing
                              Just resp -> do
                                         -- set cookie on all outgoing responses
-                                        cookie <- makeCsrfCookie token
-                                        let resp2 =  resp `with` [ addCookie cookie ]
-                                        return (Just resp2)
+                                        -- that have used the token.  (Don't
+                                        -- want to set cookie with every
+                                        -- response...). This is a fairly brute
+                                        -- force and probably inefficient
+                                        -- solution
+                                        if null $ matchLL (utf8 token) (content resp)
+                                           then return (Just resp)
+                                           else do
+                                               cookie <- makeCsrfCookie token
+                                               let resp2 =  resp `with` [ addCookie cookie ]
+                                               return (Just resp2)
 
           -- if POST request, reject if no cookie or no POST token or
           -- POST token doesn't match hash of cookie

File testsuite/Tests/Ella/Processors/Security.hs

View file
  • Ignore whitespace
 
 -- CSRF view processor
 
-csrfTestView req = return $ Just $ buildResponse [ addContent "OK"
-                                             ] emptyResponse
+csrfTestViewNoToken req = return $ Just $ buildResponse [ addContent "OK"
+                                                        ] emptyResponse
+csrfTestViewWithToken req = return $ Just $ buildResponse [ addContent $ utf8 $ csrfTokenField csrfProtection $ req
+                                                          ] utf8HtmlResponse
+
 csrfRejectionView req = return $ Just $ buildResponse [ addContent "Rejected"
                                                       , setStatus 403
                                                       ] emptyResponse
                                           , cookieSecure = False })
                  csrfRejectionView "secret"
 
-protectedView = (csrfViewProcessor csrfProtection) csrfTestView
+protectedViewWithToken = (csrfViewProcessor csrfProtection) csrfTestViewWithToken
+protectedViewNoToken = (csrfViewProcessor csrfProtection) csrfTestViewNoToken
 
 aCsrfToken = "01234567890123456789"
 -- Utility function for adding a valid CSRF cookie to a Request
 testCsrfGETAllowAll =
     (do
       let req = mkGetReq "/foo/"
-      Just resp <- protectedView req
+      Just resp <- protectedViewNoToken req
       return (content resp == "OK")
     ) ~? "csrf protection allows through GET requests"
 
     (do
       let req = mkPostReq "/foo/" [("name", "val")
                                   ,("csrftoken", aCsrfToken)]
-      Just resp <- protectedView req
+      Just resp <- protectedViewWithToken req
       return (content resp == "Rejected")
     ) ~? "csrf protection disallows POST requests without CSRF cookie"
 
 testCsrfRejectMissingToken =
     (do
       let req = mkPostReq "/foo/" [("name", "val")] `with` [ addCsrfCookie ]
-      Just resp <- protectedView req
+      Just resp <- protectedViewWithToken req
       return (content resp == "Rejected")
     ) ~? "csrf protection disallows POST requests without CSRF token"
 
     (do
       let req = mkPostReq "/foo/" [("name", "val")
                                   ,("csrftoken", "x")] `with` [ addCsrfCookie ]
-      Just resp <- protectedView req
+      Just resp <- protectedViewWithToken req
       return (content resp == "Rejected")
     ) ~? "csrf protection disallows POST requests when cookie doesn't match token"
 
     (do
       let req = mkPostReq "/foo/" [("name", "val"),
                                    ("csrftoken", aCsrfToken)] `with` [ addCsrfCookie ]
-      Just resp <- protectedView req
+      Just resp <- protectedViewNoToken req
       return (content resp == "OK")
     ) ~? "csrf protection allows POST when cookie matches token"
 
 testCsrfSetsOutgoingCookie =
     (do
       let req = mkGetReq "/foo/"
-      Just resp <- protectedView req
+      Just resp <- protectedViewWithToken req
       case cookies resp of
           [] -> return False
           (c:cs) -> return (cookieName c == "csrf")
 testCsrfSetsSameOutgoingCookie =
     (do
       let req = mkGetReq "/foo/" `with` [ addCsrfCookie ]
-      Just resp <- protectedView req
+      Just resp <- protectedViewWithToken req
       case cookies resp of
           [] -> return False
           (c:cs) -> if (cookieName c == "csrf")
                       else return False
     ) ~? "csrf protection sets same outgoing cookie as incoming one, if it exists"
 
+-- If the token isn't used in outgoing response, the view processor should not
+-- add the cookie.  This is to stop cookies being sent with every request.
+testCsrfNoOutgoingCookieIfNoToken =
+    (do
+      let req = mkGetReq "/foo/"
+      Just resp <- protectedViewNoToken req
+      return ((length $ cookies resp) == 0)
+    ) ~? "csrf protection doesn't send outgoing cookie if token not in response content"
+
 testCsrfSetsTokenInRequestEnv =
     (do
       let req = mkGetReq "/foo/"
              , testCsrfAcceptMatchingToken
              , testCsrfSetsOutgoingCookie
              , testCsrfSetsSameOutgoingCookie
+             , testCsrfNoOutgoingCookieIfNoToken
              , testCsrfSetsTokenInRequestEnv
              , testCsrfTokenField
              ]