3 ms·
Lately I see this approach used more and more. I really don't understand what the big gain is. Sure, you save yourself a few database round trips, but is that
by jjarmoc 12y ago
Lately I see this approach used more and more. I really don't understand what the big gain is. Sure, you save yourself a few database round trips, but is that really that big a deal?
Compare that savings with the additional size of URL parameters (increasing network overhead) in addition to the processing overhead of encryption/signing/validation. Have we really made a performance gain? I don't have any hard numbers, but I'm willing to wager than at gains are fairly negligable.
From a security standpoint, I'm not fond of this approach, and I don't think it's benefits outweight the added risks. Here's why:
- Encryption (including simple signing) is really easy to get wrong in subtle ways. A simple mistake like using SHA256() instead of HMACSHA256() breaks the system badly.
- A corralary to the first point is that using a strong key is vital. The article says nothing of the criteria a strong key should meet and a poor key could enable additional attacks. While I'm not aware of anything allowing for HMAC key recovery in better than brute force, this could be problematic. If your key is a random 10 character ASCII string, it falls to brute force in minutes at most. If it's 256 random, you're much better off.
- This scheme introduces a significant number of anonymous user-supplied inputs to the application which didn't previously exist. Each of these is a potential place for things to go wrong, which can give rise to other vulnerabilities (XSS, SQLi, etc) depending on what you're doing with that input. You'll need to be sure that you validate the signature prior to doing anything with these unverified inputs, in addition to performing sanity checks on their contents prior to signing. Part of secure application design is to minimize attack surface and complexity - this does the opposite.
- Once a signature is created, it can't be revoked. I mean, you could store the signature in the database and validate it's presence on receipt, or include in your signed values a nonce that's stored in the database, but that kind of defeats the goal of avoiding database storage. This is something the article tries to address by signing over things like an expiry time, the user's old password digest, etc. But that's insufficent as well (I'll get into why below).
- While I'm not aware of anything allowing for HMAC key recovery in better than brute force, choosing a secure key is still important. The article says nothing of the criteria a strong key should meet and a poor key could enable additional attacks.
- Even if you get everything right, key exposure is fatal. You've introduced a single point of failure in the system that didn't previously exist, and a fairly catostrophic one. If the app ever leaks that key, you're only option is to rotate it (assuming you detect the exposure), which invalidates any existing signed values.
That's the broad points, let's look at the article's examples:
http://myapp.com/resetPassword?userId=johnnysmith&expirationTime=1356156000&token=%SECURITYHASH%
Okay, so we have an irrevokable reset token for johnnysmith valid until 12/22/2012. The article doesn't mention where the key comes from. Since the key is the only thing johnny doesn't know, he can attempt as many keys as he wants offline. If he gets a hit, he can now create a valid token for any account he wants. NOT GOOD If the key is sufficiently long and randomly generated, this becomes much more difficult to the point of infeasibility but if the key is weak, so too is the overall security.
As the article notes the token can also be reused multiple times, even if johnny has already used it. They offer a means of fixing this:
HMAC256("userId=johnnysmith&expirationTime=1356156000&oldBcryptHash=$oldBcryptHash&clientIpAddress=$clientIpAddress", $mySuperSecretKey)
Okay, so with the bcrypt digest covered under the hash, a change of password will cause the signature to no longer match. But now on verification we have to pull the user's hash before calculating the HMAC, so we've added a database read back which reduces the perceived benefits.
Furthermore, this token is still valid if Johnny requests a password reset, and while waiting for the email to arrive remembers that he changed it from 'Password123' to 'Password456' and logs in. There's then a window of time where that token remains valid (until expirationTime arrives) and can be abused. The article doesn't address this case at all. I suppose they'd say that on issuing a token, you should disable login to Johnny's account until he's reset, but how would you do this? Well, you'd set a flag in that database on Johnny's record, and we're kind of back where we started again with both a database read and write, but with added cryptographic overhead and attack surface too.
The next example talks about account registration. It states that you can verify a user's email account by sending them a link to:
http://myapp.com/createAccount?email=johnnysmith@gmail.com&token=%SECURITYHASH%
This doesn't concern me as much as password recovery, since account creation is likely to be fairly anonymous anyhow and not nearly as security sensitive. Still, it's not technically true that the token validates your receipt of the email. Instead, it validates your receipt of the email, or your knowledge of the key and values used to generate the token. In practice, this is likely a small difference, but a truly random token stored in the database would require database access or visibility into the email to obtain, whereas this requires only knowledge of the key instead.
Case Study 3 talks about one-time-use and expiring resources. There's not enough detail here about their scheme for me to really critique, but they point out AWS/S3 use of HMAC signing as an example. Except that AWS/S3 signing isn't about one-time use or expiration. Instead, it's about authenticity.
AWS operates with an ACCESS_KEY and SECRET_KEY which are a pair of tokens related to each other. Requests with 'params' are signed as:
Signature = HMAC(ACCESS_KEY + params, SECRET_KEY)
Signature, ACCESS_KEY, and params are sent to the server. SECRET_KEY is the HMAC signing key. On the server side, AWS presumbaly does a DB lookup for your ACCESS_KEY, obtains the corresponding SECRET_KEY, and validates the signature matches. This authenticates that the requestor has knowledge of the SECRET_KEY, but it implies the existing of a database lookup. If you think of the ACCESS_KEY as a username, you'll see that this is really authenticating each request, as well as preventing tampering. You can issue keypairs that're good for only one use, or expire after a set time period, etc. but there's nothing implicit in the HMAC scheme that causes this. Interestingly, this scheme almost certainly requires storing the token in a server-side database the exact thing the author wants to prevent.
This can be done reasonably well, but it's a minefield to navigate when we have a tried and true solution. What's so bad about database storage of tokens? The post kind of starts with the assertion that they should be avoided, but I see no clear explanation as to why.
- jjarmoc 12y agoOkay, wow... forgive my typos and grammatical errors. Rage replying :)