我遇到了一个安全问题。请看下面的伪代码
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);
}
你们中的一些人可能已经看到了这个洞。攻击者可能会通过网络执行时间攻击 - 检测答案返回的速度,并根据检测数据库中是否有用户进行检测!知道了这一点后,攻击者现在可以检查他已经知道存在的用户的各种密码!
如果您没有看到问题或者不相信它是一个问题(这是针对哈希,但类比问题在这里),请解释一下:
比较"长度常数"中的哈希值时间确保了 攻击者无法在在线系统中提取密码的哈希值 使用定时攻击,然后将其破解。
检查两个字节序列(字符串)的标准方法 同样是比较第一个字节,然后是第二个字节,然后是第三个字节, 等等。一旦找到两个字节都不相同的字节 字符串,你知道它们是不同的,可以返回负数 立即回应。如果你没有通过两个字符串 找到任何不同的字节,你知道字符串是相同的 可以返回一个积极的结果。这意味着比较两个字符串 取决于多少时间可以花费不同的时间 字符串匹配。
例如,字符串的标准比较" xyzabc"和 " abcxyz"会马上看到第一个角色是不同的 并且不会费心去检查其余的字符串。在另一 手,当字符串" aaaaaaaaaaB"和" aaaaaaaaaaZ"比较, 比较算法扫描" a"在它之前 确定字符串是不相等的。
似乎不可能进行定时攻击 一个网络。但是,它已经完成,并且已经shown to be practical。
当我发现用户不存在时,我该怎么办??我绝对应该进行更多的计算,但这里有什么好的技巧?你们在这种场景中使用了什么?
如果这有任何意义,我正在使用C#。
答案 0 :(得分:2)
解决方案很简单,无论用户是否存在,都遵循相同的代码路径。
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;
}
现在,您需要确保GetRealPasswordHash
花费相同的时间用于有效的电子邮件和无效的电子邮件,并为无效的电子邮件返回“假”哈希(“虚拟”用户的密码哈希Guvante指的是in his comment)。
这可以确保GetHash
和SlowEquals
仍然使用有效数据进行调用,但由于userExists
为假,您会忽略其结果。
但是我也同意Guvante的第二点,任何正常的系统都会在尝试多次无效密码后锁定用户帐户。这可以防止对用户登录的暴力/字典攻击。您只能解锁登录锁定,直到一段时间过后或用户呼入并验证其身份。