在查询索引数据库字段时防止定时攻击的一种方法是在服务器端散列值。这类似于攻击者在获得密码哈希后无法登录的情况,因为服务器在检查数据库之前仍会应用哈希。
正如marstato的回答所指出的那样,原始代码(来自上面的问题)有很多问题,所以让我们从这段代码开始:
$token = $_COOKIE['token'];
$db->query("SELECT COUNT(*) FROM sessions WHERE token = ?", $token);
攻击者确实可以逐步找到有效的令牌。这通常很难或不可能,但从理论上讲,这是可行的。攻击者会查询“A”,服务器会向数据库询问“A”。然后他们尝试“B”,服务器向数据库询问“B”。如果“A”更快,他们现在尝试“AA”和“AB”。如果速度较慢,他们可能会尝试“BA”和“BB”等。还有更多内容,但概括一下定时攻击的一般概念,这就是它的工作原理。
要解决此问题,您可以这样做:
$secret = 'qJHcsgkED0egeuljhsZr'; // randomly generated
$hashed_token = hash_hmac('sha256', $_COOKIE['token'], $secret);
$db->query("SELECT COUNT(*) FROM sessions WHERE hashed_token = ?", $hashed_token);
让我们再次尝试攻击:攻击者尝试“A”,服务器在数据库中查询“a661024ef...”。攻击者尝试“B”,服务器在数据库中查询“5407624cb ...”。时间是相等的(或至少是随机的),因为两者都不存在,也永远不会引导攻击者获得有效的令牌。
当然,这取决于$secret剩余的秘密。最好的做法是定期更改此设置,以便老员工无法知道秘密并应用攻击,但这确实意味着您还必须更改数据库中的所有值。如果数据库可以执行 HMAC,您可以通过执行以下操作来解决此问题:
$secure_rnd = openssl_random_pseudo_bytes(16);
$randomized_token = hash_hmac('sha256', $_COOKIE['token'], $secure_rnd);
$db->query("SELECT COUNT(*) FROM sessions WHERE hmac(token, '$secure_rnd') = ?", $randomized_token);
在安全方面,这是最好的方法,原因有以下三个:
- 虽然它在速度上与非索引字段相当,但它可以防止未来的数据库管理员意外索引该字段。通常,人们会在用于查找时索引一个字段。这会导致定时攻击,但是你会如何告诉你未来的数据库管理员呢?最好有代码,这样字段是否被索引都没有关系
- 如果该字段仅仅是非索引的,仍然可能存在攻击:数据库将遍历每一行并比较值。遍历每一行并比较第一个字符是 O(n),但如果数据库比较更多字符(因为有多个字符匹配),则需要稍长的时间,因此攻击者可以(再次,理论上)观察到这种较慢的响应并了解到他们猜对了任何标记的不止一个字符。使用
$secretor$secure_rnd不易受此影响。
- 它不依赖于
$secret旧系统管理员可能知道的静态。
缺点是它是一个 O(n) 慢查询。
一种混合方法,在避免使用 static 的同时利用索引$secret,可能是使用$secret每天更改的 a。在每晚的过渡期间,应用服务器可以先尝试今天的秘密,然后作为后备,尝试昨天的秘密。由于您的令牌以及散列方法应该是安全的(否则您会遇到更大的问题),因此寻找两个值永远不会导致与其他令牌的冲突。更新每条数据库记录后,它就可以停止查询旧的$secret. 更新数据库的 cron 作业可以将机密和升级状态写入配置文件。
也就是说,我从未听说过有人使用这样的定时攻击。比较像 PHP 这样的语言的密码,是的,但不是数据库查询。我知道的大多数会话令牌系统都以这种理论上不安全的方式工作,但它们似乎从未被黑客入侵。这并不意味着它是不可能的,特别是如果它在理论上看起来 100% 可能,那么迟早会有人让它在实践中发挥作用。虽然我不确定今天对于小型网站是否需要所有这些努力,但保护高安全性系统会很好。