Aside from the replay attacks discussed, there are some other attack vectors on the cookie_session store.
I appreciate (and admire!) Jeremy's good humor on all of this:
Planting the seed here led to quick ripening and plenty of pesticide. Thanks for the fish, all.
jeremy
Anyway, here's what we came up with:
1. Brute Force SHA512 can be computed _very_ fast. On my Pentium Core Duo:
z = 'z' * 100; puts Benchmark.measure { 1000.times
{ Digest::SHA512.hexdigest(z) }} 0.032000 0.000000 0.032000 ( 0.031000)
That's 0.03 ms/hash using simple Ruby code, not optimized C / Assembly!
That means a simple brute force attack can go through the entire dictionary in a few seconds. Even using multiple word phrases, and inserting several digits, we can complete the attack in reasonable time. We can even search brute force letter combinations, esp. if we only generate pronouncable phoneme combinations.
2. Entropy Related to #1: to resist brute force attacks, you really want 128 bits, and preferably 256 bits, of entropy. The source code suggests "some secret phrase", which is unlikely to come even close. The way to create a key is to use a PRNG seeded with true, system level entropy.
3. Rainbow Tables Since there will be standard data common among many apps (eg null sessions), and no salting is employed, we can use create Rainbow Tables, and afterwards find the secret very quickly.
4. Precomputation Cookie session computes the hash by *appending* the secret to the data. This can be used to speed up the brute force, by precomputing the hash of the data, and starting the hash function on the candidate session. The correct way to use the key is to repeatedly XOR it to the data, not append it.
5. Key Storage One of the most common crypto truisms developers know is "Don't store passwords in the clear". Backups are made, files are transferred, and the bad guys job is made too easy. In cookie_store, the app's key is stored directly in the clear. No ephemeral key is used. If an attacker see's this, it's equivalent to getting *everyone's* password. Even worse, since he can create arbitrary sessions.
To make matters worse, the source code seems to suggest storing the key in the app's source code itself. So, every developer, every subversion check out, every code base back up, has the key.
6. Key Expiry There's no way to expire the current key without destroying all of the current sessions. Keys are intended to be (relatively) permanent and require manual actions to change. This makes all of the above attacks both easier and more dangerous.
7. Key / Session Revocation Likewise, should you suspect key compromise - to revoke the key, you need to destroy all of the current sessions. And there's also no way to revoke an individual session, should you get a call "uh, I logged in at the library yesterday and now someone's reading my mail".
Some of the above can be corrected easily. Some are much more challenging. I think they all should demonstrate that creating a crypto system is quite formidable.
Below is a simple proof of concept code to demonstrate #1. It's simple Ruby: an optimized native version could be expected to be 100 times faster.
# cookie_crumbler.rb
include 'base64' include 'digest/sha2'
cookie = ARGV[0] data, digest = cookie.split('--')
# You can replace this with any object supporting #each, # such as a brute force generator wordlist = File.open('/usr/share/dict/words')
wordlist.each do |line| secret = line.chomp if Digest::SHA512.hexdigest(data + secret) == digest puts "Secret found: #{secret.inspect}" end end