Xing \ Creative \ Coding

Web Application Software Development

If you sign each request as described in the post Security Without SSL, Creating a Shared Secret to Sign Requests, you can be fairly confident that a passive MitM attacker won’t be able to modify the user’s request. However, they can potentially “replay” the request over and over again. Imagine if an attacker were able to replay Amazon’s 1-click purchase request over and over again and instead of paying for and getting 1 hard-drive like you wanted, you get 100. Without SSL, a passive attacker can replay the same post over and over again, and if you don’t defend against it, it can potentially create a lot of havoc that could ruin your relationship with your users. So, we need a way to prevent replay.

On the simple side, you can just have the client put a Request Time into each request. Then, when you receive the request on the server, you can validate that the time is within a reasonable grace period of the Request Time. If the request is older than the grace period allows, then you can reject the message. However, this relies on the user’s clock being set right, and a replay attack could still occur during the grace period.

So, the better way is to use a nonce. In some cases, the client requests a nonce from the server, and then is allowed to use that nonce one time. However, for this case, I’d say to do something easier. Since we already have a unique string (the signature we created from the shared secret), we can simply never allow the same signature to be used twice. This will reduce the overhead on the server and make the client’s life much easier since it won’t have to request any nonces. Then, since we’ll probably want to keep our nonce database small so that we aren’t looking through millions of records on each request, we can delete any records older than 24 hours or something. However, once we start deleting records, we need to do Request Time checking. If we keep records in the database for 24 hours, then we just need to ensure that all requests coming in are less than 24 hours old (or whatever timeframe we use to store nonces). This way, we can keep our database table of nonces smaller and still rest assured that a request from 5 days ago can’t be replayed ever again.

So, you may wonder what happens if the same user makes the same GET request twice in a row and produces the same signature hash. Well, obviously, the second request would get blocked. However, it seems that there is an overall application problem if the same person requests the same data twice within the same second. If the requests are even 1 second apart, the nonce will be completely different because the RequestTime in the request would change. However, if you’re still worried about it, just add a milliseconds field to the request, the server doesn’t even have to know what the field is for, it just has to make the request signature unique.

As far as other legitimate requests, the odds of producing the same signature on two different requests (hash collision) with a hashing algorithm like SHA256 or better that produces at least 64 characters is so vanishingly small that I would place more bets on you being the first to create FTL warp drive. So, I wouldn’t worry about it. Worst case scenario, the client script will have to make a second request when the first fails, and the time will have changed by then, and there won’t be another hash collission until our sun dies out 5 billion years from now.

February 9th, 2015

Posted In: Security

Tags: , , ,

One Comment

Prerequisites: To sign requests with a shared secret, you will have to force your users to enable JavaScript. This may alienate some users, and ultimately I’m not sure it’s worth the effort just to avoid purchasing an SSL cert. But maybe for some open source apps that have heavy JavaScript dependencies anyway, this might be a good fit since you can’t control whether your users will get a cert..
Strengths: A request signed with a Shared Secret prevents session hijacking by passive attackers (e.g. Firesheep)
Weaknesses: All Non-SSL security is vulnerable to MitM attacks. Also, if you use a password hash as the Shared Secret, it can potentially be compromised at registration and during password changes.

As discussed in a previous post, Why you MUST use SSL, without SSL, there is no way to prevent a Man-in-the-Middle (MitM) attack from bypassing any security measures you take. However, by using a Shared Secret to sign your requests, you can make the task more difficult for an MitM as well as reduce the risk of a passive attacker getting the password, hijacking the session, or running a replay attack. A Shared Secret is something that only the server and client know, which implies that you never send it over the wire in either direction. This can be a bit problematic. How can both sides know a shared secret without ever communicating that secret across the web?

Well, ideally, if we truly want the communication to be a shared secret, we would need to communicate it to the user via an SMS message or an e-mail. By using an alternate method, you reduce the risk of interception by any MitM or passive attacker. An SMS would probably be safest, as if their e-mail is transmitted over HTTP or unsecured POP3, then an MitM would possibly be able to see that communication as well. But, at the very least, by transmitting through another method of communication, you greatly reduce the risk of the shared secret getting intercepted in conjunction with an MitM or Side-Jacking attempt.

A slightly less secure Shared Secret is the user’s password. The reason it is less secure is because it does have to be transmitted at least once: during registration and also anytime the user changes his or her password. Anyone listening at that point in time will know the Shared Secret, and your security can be bypassed. However, this is still significantly more secure than if you don’t use a Shared Secret. The advantage to your users in this case, versus an SMS message, is that they don’t have to jump through any additional hoops beyond what they are already used to. However, if we do this, we have to make sure that the password Shared Secret is not communicated during the login…we want to make sure that it is ONLY sent during the initial registration or password change to reduce the frequency of communicating the shared secret.

How to Login Without Forfeiting the Shared Secret

Assuming that we dutifully hashed our password with a salt, the server doesn’t actually know the password, so our Shared Secret is actually the hash of salt+password rather than the password itself. As our Shared Secret, it is important that we never send the hash of salt+password to the server or vice-versa. So, when the user logs in, we must first send them TWO salts. The first passwordSalt the client will use to generate the shared secret, and the second oneTimeSalt will be used to communicate the shared secret to the server for the login.

var sharedSecret = hash( passwordSalt + password ),
    loginHash    = hash( oneTimeSalt + sharedSecret )
$.post(loginPath, {"email":email,"password":loginHash}, callback);

The server then validates in a method something like this:

isValidPassword( hashFromClient ) {
    return hash_equals(
        hash( oneTimeSalt + storedPasswordHash ),
    ); //slow, time-constant equality check

If the above method is successful, then we destroy the one-time salt and put a new one in its place for the next use. We then send the client a session token that it is able to use for the rest of its session to remain logged in. However, if the session token is all we use for validating the user on subsequent requests, then any novice MitM, or person using Firesheep, will be able to hijack the session. So, we need to use our shared secret to “sign” each request in a way that the server can ensure the data is actually from someone that knows the shared secret and hasn’t been modified.

Fortunately, this is actually quite simple. We already have our shared secret, the first hash. So, we can use the first hash to sign each request something like the following:

var signature = hash( sharedSecret + sessionToken + requestMethod + requestUrl + requestBody );

We append this signature to our request, and when the server receives the request, it can find the Shared Secret associated with that session token and then run the same hash. If the two hashes are equal, then the server can assume that the request comes from someone who knows the Shared Secret. So, about the only thing an attacker can do without knowing the Shared Secret is replay the exact same request multiple times. However, this is still potentially problematic. Imagine an attacker adding a million rows to your database by replaying the same POST request a million times…it could get ugly. So, next on the list is to look at Security Without SSL, How to Prevent Replay Attacks.

February 8th, 2015

Posted In: Security

Tags: , , ,

One Comment