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

afSleepSafe.csrfTokenName

Name of the posted form field that holds the CSRF token. Defaults to _csrfToken.

afSleepSafe.csrfTokenTimeout

How long CSRF tokens have to live. Set to null to disable timeouts. Defaults to 61min to ensure user sessions time out before tokens.

afSleepSafe.csrfPassPhrase

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 null (default) then a random pass phrase is generated each time the sever starts.

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.

validateToken

Source

Obj? validateToken(Str csrfToken)

Manually validates a given CSRF token. Returns null if valid, or an error string if invalid.