Safety of trying to login (preventing time-attack)
Safety of trying to login (preventing time-attack)
问题
I came across a security issue. Please have a look at the pseudocode below
public static bool TryLogin(string email, string password)
{
if (UserExists(email)) // here is the problem
return false;
var hash = GetRealPasswordHash(email);
var hash2 = GetHash(email, password);
return SlowEquals(hash, hash2);
}
Some of you may already see the hole. An attacker may perform a time-attack over the network - detect how fast the answer is returned and based on that detect if there is a user in the database or not! Having known that, the attacker may now check various passwords for the user he already knows that exists!
A word of explanation if you don't see the problem or don't believe it to be a problem (this is for hashes, but analogical issue is here):
Comparing the hashes in "length-constant" time ensures that an attacker cannot extract the hash of a password in an on-line system using a timing attack, then crack it off-line.
The standard way to check if two sequences of bytes (strings) are the same is to compare the first byte, then the second, then the third, and so on. As soon as you find a byte that isn't the same for both strings, you know they are different and can return a negative response immediately. If you make it through both strings without finding any bytes that differ, you know the strings are the same and can return a positive result. This means that comparing two strings can take a different amount of time depending on how much of the strings match.
For example, a standard comparison of the strings "xyzabc" and "abcxyz" would immediately see that the first character is different and wouldn't bother to check the rest of the string. On the other hand, when the strings "aaaaaaaaaaB" and "aaaaaaaaaaZ" are compared, the comparison algorithm scans through the block of "a" before it determins the strings are unequal.
It might seem like it would be impossible to run a timing attack over a network. However, it has been done, and has been shown to be practical.
What should I do then, when I detect that a user does not exist? I definitely should perform some more calculations, but what is a good technique here? What do you guys use in such scenarios?
If this is of any significance, I am using C#.
回答1
The solution is easy, you follow the same code path whether the user exists or not.
public static bool TryLogin(string email, string password)
{
bool userExists = UserExists(email);
var hash = GetRealPasswordHash(email);
var hash2 = GetHash(email, password);
return SlowEquals(hash, hash2) && userExists;
}
Now you will need to make sure GetRealPasswordHash
takes the same amount of time for a valid email and a non valid email and returns a "fake" hash for the non valid email (the "dummy" user's password hash Guvante was referring to in his comment).
This makes sure that GetHash
and SlowEquals
both still get called with valid data but you ignore their results due to userExists
being false.
However I also agree with Guvante on his second point, any normal system will lock the user account after a number of invalid password attempts. This prevents brute force/dictionary attacks against a user's login. You only unlock the lock on the login until after a period of time has passed or the user calls in and verifies their identity.
作者:Chuck Lu GitHub |