Nonce for CSRF protection
This commit is contained in:
parent
2fef1766f4
commit
1a752d4343
@ -68,6 +68,8 @@ import Network.Wai.Parse hiding (FileInfo)
|
||||
import qualified Network.Wai.Parse as NWP
|
||||
import Data.String (fromString)
|
||||
import Web.Routes
|
||||
import Control.Arrow (first)
|
||||
import System.Random (randomR, newStdGen)
|
||||
|
||||
#if TEST
|
||||
import Test.Framework (testGroup, Test)
|
||||
@ -264,7 +266,9 @@ toWaiApp' y segments env = do
|
||||
let eh er = runHandler (errorHandler' er) render eurl' id y id
|
||||
let ya = runHandler h render eurl' id y id
|
||||
(s, hs, ct, c, sessionFinal) <- unYesodApp ya eh rr types
|
||||
let sessionVal = encodeSession key' exp' host sessionFinal
|
||||
let sessionVal = encodeSession key' exp' host
|
||||
$ (nonceKey, reqNonce rr)
|
||||
: sessionFinal
|
||||
let hs' = AddCookie (clientSessionDuration y) sessionName
|
||||
(bsToChars sessionVal)
|
||||
: hs
|
||||
@ -328,7 +332,27 @@ parseWaiRequest env session' = do
|
||||
Nothing -> langs''
|
||||
Just x -> x : langs''
|
||||
rbthunk <- iothunk $ rbHelper env
|
||||
return $ Request gets' cookies' session' rbthunk env langs'''
|
||||
nonce <- case lookup nonceKey session' of
|
||||
Just x -> return x
|
||||
Nothing -> do
|
||||
g <- newStdGen
|
||||
return $ fst $ randomString 10 g
|
||||
return $ Request gets' cookies' session' rbthunk env langs''' nonce
|
||||
where
|
||||
randomString len =
|
||||
first (map toChar) . sequence' (replicate len (randomR (0, 61)))
|
||||
sequence' [] g = ([], g)
|
||||
sequence' (f:fs) g =
|
||||
let (f', g') = f g
|
||||
(fs', g'') = sequence' fs g'
|
||||
in (f' : fs', g'')
|
||||
toChar i
|
||||
| i < 26 = toEnum $ i + fromEnum 'A'
|
||||
| i < 52 = toEnum $ i + fromEnum 'a' - 26
|
||||
| otherwise = toEnum $ i + fromEnum '0' - 52
|
||||
|
||||
nonceKey :: String
|
||||
nonceKey = "_NONCE"
|
||||
|
||||
rbHelper :: W.Request -> IO RequestBodyContents
|
||||
rbHelper = fmap (fix1 *** map fix2) . parseRequestBody lbsSink where
|
||||
|
||||
@ -12,6 +12,8 @@ module Yesod.Form
|
||||
, FormFieldSettings (..)
|
||||
, Textarea (..)
|
||||
, FieldInfo (..)
|
||||
-- ** Utilities
|
||||
, formFailures
|
||||
-- * Type synonyms
|
||||
, Form
|
||||
, Formlet
|
||||
@ -92,13 +94,30 @@ fieldsToDivs = mapFormXml $ mapM_ go
|
||||
clazz fi = if fiRequired fi then "required" else "optional"
|
||||
|
||||
-- | Run a form against POST parameters.
|
||||
runFormPost :: GForm s m xml a -> GHandler s m (FormResult a, xml, Enctype)
|
||||
--
|
||||
-- This function includes CSRF protection by checking a nonce value. You must
|
||||
-- therefore embed this nonce in the form as a hidden field; that is the
|
||||
-- meaning of the fourth element in the tuple.
|
||||
runFormPost :: GForm s m xml a -> GHandler s m (FormResult a, xml, Enctype, Html)
|
||||
runFormPost f = do
|
||||
rr <- getRequest
|
||||
(pp, files) <- liftIO $ reqRequestBody rr
|
||||
runFormGeneric pp files f
|
||||
nonce <- fmap reqNonce getRequest
|
||||
(res, xml, enctype) <- runFormGeneric pp files f
|
||||
let res' =
|
||||
case res of
|
||||
FormSuccess x ->
|
||||
if lookup nonceName pp == Just nonce
|
||||
then FormSuccess x
|
||||
else FormFailure ["As a protection against cross-site request forgery attacks, please confirm your form submission."]
|
||||
_ -> res
|
||||
return (res', xml, enctype, hidden nonce)
|
||||
where
|
||||
nonceName = "_nonce"
|
||||
hidden nonce = [$hamlet|%input!type=hidden!name=$nonceName$!value=$nonce$|]
|
||||
|
||||
-- | Run a form against POST parameters.
|
||||
-- | Run a form against POST parameters. Please note that this does not provide
|
||||
-- CSRF protection.
|
||||
runFormMonadPost :: GFormMonad s m a -> GHandler s m (a, Enctype)
|
||||
runFormMonadPost f = do
|
||||
rr <- getRequest
|
||||
@ -106,9 +125,14 @@ runFormMonadPost f = do
|
||||
runFormGeneric pp files f
|
||||
|
||||
-- | Run a form against POST parameters, disregarding the resulting HTML and
|
||||
-- returning an error response on invalid input.
|
||||
-- returning an error response on invalid input. Note: this does /not/ perform
|
||||
-- CSRF protection.
|
||||
runFormPost' :: GForm sub y xml a -> GHandler sub y a
|
||||
runFormPost' = helper <=< runFormPost
|
||||
runFormPost' f = do
|
||||
rr <- getRequest
|
||||
(pp, files) <- liftIO $ reqRequestBody rr
|
||||
x <- runFormGeneric pp files f
|
||||
helper x
|
||||
|
||||
-- | Run a form against GET parameters, disregarding the resulting HTML and
|
||||
-- returning an error response on invalid input.
|
||||
@ -225,3 +249,7 @@ toLabel (x:rest) = toUpper x : go rest
|
||||
go (c:cs)
|
||||
| isUpper c = ' ' : c : go cs
|
||||
| otherwise = c : go cs
|
||||
|
||||
formFailures :: FormResult a -> Maybe [String]
|
||||
formFailures (FormFailure x) = Just x
|
||||
formFailures _ = Nothing
|
||||
|
||||
@ -143,7 +143,7 @@ crudHelper
|
||||
-> GHandler (Crud master a) master RepHtml
|
||||
crudHelper title me isPost = do
|
||||
crud <- getYesodSub
|
||||
(errs, form, enctype) <- runFormPost $ toForm $ fmap snd me
|
||||
(errs, form, enctype, hidden) <- runFormPost $ toForm $ fmap snd me
|
||||
toMaster <- getRouteToMaster
|
||||
case (isPost, errs) of
|
||||
(True, FormSuccess a) -> do
|
||||
@ -166,6 +166,7 @@ crudHelper title me isPost = do
|
||||
^form^
|
||||
%tr
|
||||
%td!colspan=2
|
||||
$hidden$
|
||||
%input!type=submit
|
||||
$maybe me e
|
||||
\ $
|
||||
|
||||
@ -108,6 +108,8 @@ data Request = Request
|
||||
, reqWaiRequest :: W.Request
|
||||
-- | Languages which the client supports.
|
||||
, reqLangs :: [String]
|
||||
-- | A random, session-specific nonce used to prevent CSRF attacks.
|
||||
, reqNonce :: String
|
||||
}
|
||||
|
||||
lookup' :: Eq a => a -> [(a, b)] -> [b]
|
||||
|
||||
@ -61,7 +61,7 @@ getRootR = defaultLayout $ flip wrapWidget wrapper $ do
|
||||
addHead [$hamlet|%meta!keywords=haskell|]
|
||||
|
||||
handleFormR = do
|
||||
(res, form, enctype) <- runFormPost $ fieldsToTable $ (,,,,,,,,,,)
|
||||
(res, form, enctype, hidden) <- runFormPost $ fieldsToTable $ (,,,,,,,,,,)
|
||||
<$> stringField (FormFieldSettings "My Field" "Some tooltip info" Nothing Nothing) Nothing
|
||||
<*> stringField ("Another field") (Just "some default text")
|
||||
<*> intField (FormFieldSettings "A number field" "some nums" Nothing Nothing) (Just 5)
|
||||
@ -103,7 +103,12 @@ textarea.html
|
||||
height: 150px
|
||||
|]
|
||||
wrapWidget form $ \h -> [$hamlet|
|
||||
$maybe formFailures.res failures
|
||||
%ul.errors
|
||||
$forall failures f
|
||||
%li $f$
|
||||
%form!method=post!enctype=$enctype$
|
||||
$hidden$
|
||||
%table
|
||||
^h^
|
||||
%tr
|
||||
|
||||
Loading…
Reference in New Issue
Block a user