Xing \ Creative \ Coding

Web Application Software Development

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