似乎for a while,Unix系统上的登录实用程序仅在存在有效用户名时才计算哈希值;这打开了一个安全漏洞,允许进行定时攻击,因为用户可以通过生成散列密钥进行比较所需的时间来判断何时找到了用户名。
这对桌面应用程序有意义,但它对Web应用程序也有意义吗?我倾向于这样做,但这种修复是必要的吗?
例如,在Django auth模块中:
class MyBackend(ModelBackend):
def authenticate(self, email=None, password=None):
try:
user = User.objects.get(email=email)
return user if user.check_password(password) else None
except User.DoesNotExist:
User().check_password(password) # is this line necessary?
return None
额外的哈希计算是否适合这种情况?如果我在auth呼叫上使用速率限制,这是否会降低像这样的定时攻击的可能性?
答案 0 :(得分:10)
最初的定时攻击Tenex攻击与散列无关 - 它通过定位密码使其跨越页面边界导致虚拟内存缓存未命中。攻击者可以尝试定位一系列密码,以便只有第一个字符位于第一页中,并且当验证花费足够长的时间以便发生缓存未命中时,它会知道第一个字符匹配。攻击者可以为密码中的每个字符重复此操作。
除非攻击者控制内存中输入的细粒度定位,否则,对密码的定时攻击不是问题,而是任何超线性的秘密检查算法w.r.t.秘密的长度(> = O(秘密长度))可能泄漏有关密码长度的信息。
如果你小心比较密码中的所有字符而不管成功与否,那么你也可以击败攻击:
boolean match = true;
for (int i = 0; i < min(salted_from_db_length, salted_from_user_length); ++i) {
if (salted_from_db[i] != salted_from_user[i]) {
match = false;
//break; // Stopping early leaks info.
}
}
match = salted_from_db_length == salted_from_user_length && match;
您应该进行测试,确保编译器优化不会将时序漏洞放回代码中。
请注意,术语“计时攻击”也用于其他环境,这些可能会影响Web应用程序。例如,当系统时钟用于在两个不应该合谋的进程之间构造covert channel时 - 在两个不同的源中加载的javascript可以通过使用间隔来检查时间和循环来建立低带宽信道使用处理器或不使用处理器。
答案 1 :(得分:2)
这是一个很好的问题,是的,您应该考虑采取措施确保为无效用户名返回“登录被拒绝”响应的时间与密码错误的有效用户名相比。
在已知用户名以便通过分析服务器进行字符串比较所花费的时间来发现哈希值并最终发现密码本身的情况下,已经使用了定时攻击,因此使用'slow_equals'函数用于比较哈希值,并且在找到不匹配时不会提前退出。
登录失败后,您永远不应该报告是否是不正确的用户名或密码,因为这样做会帮助攻击者发现有效的用户名(尽管您应该告知用户为什么不告诉他们这是他们错误的用户名或密码,因为它往往会激怒他们。)
随机攻击者需要发现有效的用户名,然后发现它们的密码。如果你帮助他们发现有效的用户名,他们就在那里。因此,如果他们可以根据您的登录脚本返回失败所花费的时间发现有效的用户名,那么您将会帮助他们。
让我们考虑一下这个过程: 1.数据库查询以检索哈希和盐; 2.用盐哈希密码; 3.将检索到的哈希与提供的密码生成的哈希进行比较。
查询是否在找到匹配记录时提前退出?如果我们为username列指定了UNIQUE KEY,它可能会这样做,所以我们可能不应该这样做,而是有其他机制来避免在我们修改数据库时出现重复。
如果数据库查询没有找到记录,我们是否会执行这些后续步骤? (你的哈希过程需要花费一点时间吗?)因此,如果用户名不匹配,则应使用“默认”哈希和盐,以便在恒定时间内完成该过程[这就是Q]中的异常处理程序代码。
在这里使用'slow_equals'。
此处的回答者建议网络延迟差异使其成为一个无问题。我认为攻击者不需要100%的成功率,而且统计分析将会显露出来。你不能太小心。
答案 2 :(得分:1)
可能不是一个明确的答案,但似乎您可以确保所有用户名检查操作花费相同的时间。让它们在完成后进入等待状态,直到经过预定的时间。