justinas / nosurf

CSRF protection middleware for Go.

Home Page:http://godoc.org/github.com/justinas/nosurf

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Employ techniques to mitigate BREACH.

justinas opened this issue · comments

The CSRF token as it is now might be acquired by an attacker using the BREACH technique (assuming the server has compression turned on).

breach-mitigation-rails and django-debreach both take up an interesting approach with this, encrypting the CSRF token with a new random string on each request. It seems like this could be easily applied to nosurf.

There's a good discussion here: https://code.djangoproject.com/ticket/20869

It seems that either a simple XOR or AES are equally sufficient: XOR is probably faster, although that's a guess. I'm not sure how low-level Go's AES implementation is.

I might see if I can hack together a quick patch for this (it'll be useful for my own CSRF middleware) when I have some time this weekend. Feel free to beat me to it, though!

Note: I've run some quick and dirty tests for masking CSRF tokens per-request using a few of the "popular" methods addressed by the Rails gem (XOR), the django-debreach (AES) and in the Django discussion linked above.

BenchmarkXOREncode        500000              3231 ns/op
BenchmarkXORDecode        500000              3017 ns/op
BenchmarkHMACHash         500000              6387 ns/op
BenchmarkHMACCompare      500000              6028 ns/op
BenchmarkAESCBCEncrypt    500000              4465 ns/op
BenchmarkAESCBCDecrypt    500000              4532 ns/op

You can find the source here: https://gist.github.com/elithrar/172705ee03c4fed068af

My opinion would be that the difference, on a per-request basis, is ultimately pretty small: 0.003017ms to decode an XORed token vs. 0.006028ms to decode a HMACed token. The downside to the AES route is the output size is going to be longer—and there's a fair bit more code complexity (you need to pad, unpad & generate an IV) for ultimately no gain over just vanilla XORing. XORing also allows you to keep your current middleware mostly "as is", too.

Thanks for running the tests! Sorry for not having told you before, but
I've actually begun implementing the encryption using one time pad, just
haven't pushed the branch yet. Still have to fit it in the current API
nicely.

OTP seems like the way to go from your benchmarks as well.

@justinas No problems: I'm implementing this (more cleanly than my Gist, mind you!) for my own [tiny] CSRF middleware, but figured you would benefit from the benchmarks as well. I was mostly curious to determine why XOR seemed the favorite.

Well as the token is a short string, there's no need for a block cipher
like AES, I think, as the key for OTP will be short as well. And, like you
said, OTP is simpler.

I've also been thinking of writing benchmarks for common operations (get,
get then post, etc., with and without nosurf) and then comparing the breach
branch with the master now. This should help measure the real impact of
encryption in the request handling lifecycle.

An (apparently) working implementation of encrypted tokens is now on the breach branch. Tests do pass, but another pair of eyes wouldn't hurt.

Benchmarks would also be useful, though I haven't finished them yet, as the test HTTP server has a relatively high overhead and thus makes the results useless and extracting the cookies directly from ResponseWriter is PITA. I'll most likely finish writing them using the latter way.

Eh, what the hell, masked tokens are up on develop and master (6f31678).