const classafSleepSafe::CsrfTokenGuard
sys::Obj afSleepSafe::CsrfTokenGuard : afSleepSafe::Guard
Guards against CSRF attacks by enforcing an customisable Encrypted Token Pattern strategy.
Overview
Cross Site Request Forgeries (CSRF) are a very specific type of attack vector.
Think of it as someone stealing your application URLs such as http://example.com/logout
or http://example.com/buyProduct/XXXXXX
and tricking people in to clicking them, either though emails, fake HTML image links (<img src="http://example.com/buyProduct/XXXXXX">
), or other means. If the user happens to be logged in to your site, then the browser will happily send the fake request and BOOM before the user realises it, he's just bought a Sex Doll!
But it's not just HTTP GET
requests, browsers will happily POST
form data across domains too. In fact, GET
requests should never affect server state, they should just get content. Any kind of logout, delete, or buy action should be performed over a POST
request with for data. So now we just just need to protect POST
requests.
To protect against CSRFs, SleepSafe generates a unique token per HTTP request that should be embedded in every HTML form. This token is an encrypted hash of a timestamp, the user's session ID (if one exists) and any other information you care to add. When the HTML form is submitted, the token is retrieved, decrypted, and values compared against the user's existing credentials. The request is rejected should any values mis-match and, optionally, if the token has since expired (expiry defaults to 1 hour).
To circumnavigate this, an attacker would have to steal a CSRF token value from an already authenticated user. The only way to do this is by either packet sniffing or injecting their own scripts via Cross Site Scripting (XSS) and immediately tricking a targeted user. All of which is hard to do over HTTPS and is outside of the scope of CSRF protection.
Note that encryption is performed with 128 bit AES which would take my dev machine 100 septillion (10^24) years to crack with a standard brute force attack algorithm.
Specifics
When rendering a HTML form you must include the following input:
<input type="hidden" name="_csrfToken" value="XXXX-XXXX-XXXX-XXXX">
where value
is a Str
obtained from:
token := httpRequest.stash["afSleepSafe.csrfTokenFn"]->call()
SleepSafe adds the CSRF token generation function to the stash at the start of every request.
Note that FormBean will automatically add the hidden input, complete with token, to every rendered form.
When the HTML form is submitted SleepSafe inspects all POST requests with a content type of:
application/x-www-form-urlencoded
multipart/form-data
text/plain
and checks and validates the _csrfToken
token value.
Other content types can not be submitted by HTML forms and as such, are not subject to CSRF attacks, and are not checked by SleepSafe.
Multi-Part Form Uploads
SleepSafe will parse multipart form-data looking for the CSRF token. But in doing so note that the entire HTTP Request body (form data) will be cached in memory and parsed twice, once by SleepSafe and again by your application - which may represent an overhead.
If this is not desirable, then you may also append the CSRF token as a URL query parameter. Although this may constitute a minor security flaw / inconvenience as request URLs are often logged by applications and proxies.
Ioc Configuration
afIocConfig Key | Value |
---|---|
| Name of the posted form field that holds the CSRF token. Defaults to |
| How long CSRF tokens have to live. Set to |
| The pass phrase used to generate the encryption secret key. Generated CSRF tokens can only be used across server restarts if this value is set. If |
Example:
@Contribute { serviceType=ApplicationDefaults# } Void contributeAppDefaults(Configuration config) { config["afSleepSafe.csrfTokenName"] = "clickFast" config["afSleepSafe.csrfTokenTimeout"] = 2sec config["afSleepSafe.csrfPassPhrase"] = "Fantom Rocks!" }
To disable CSRF checking, remove this class from the SleepSafeMiddleware
configuration:
@Contribute { serviceType=SleepSafeMiddleware# } Void contributeSleepSafeMiddleware(Configuration config) { config.remove(CsrfTokenGuard#) }
To add custom data to the CSRF token hash:
@Contribute { serviceType=CsrfTokenGeneration# } private Void contributeCsrfTokenGeneration(Configuration config) { config["user"] = |Str:Obj? hash| { hash["user"] = "Princess Daisy" } }
Then to verify the custom data in the token hash:
@Contribute { serviceType=CsrfTokenValidation# } private Void contributeCsrfTokenValidation(Configuration config) { config["user"] = |Str:Obj? hash| { if (hash.containsKey("user")) if (hash["user"] != "Princess Daisy") throw Err("User is not a Princess!") } }
Any error thrown will be picked up by SafeSheet and converted to a 403 Forbidden
response.