软件开发
PHP 中的定时攻击:一个实用的例子--为什么 "hash_equals() "很重要?
您想了解在 Wordpress/Laravel/PHP 中对 API 标记进行定时攻击的真实示例吗?我们将在这里为您介绍,包括应对定时攻击的技巧和最佳实践措施。
许多开发人员在检查 API 密钥、令牌或其他秘密字符串时,依赖于使用===或== 进行简单比较。乍一看,这似乎很合理,但其中有一个问题:比较中的时间差可能会被攻击者利用。
本文将以一个真实的例子来说明定时攻击在概念上是如何起作用的。
真实场景(简化示例)
设想一个内部微服务接受带有查询参数令牌的请求,并将其与存储在环境中的 GitHub API 令牌进行比较。
不安全代码(简化):
$expected = getenv('GITHUB_API_TOKEN');$provided = $_GET['token'] ?'';if($provided === $expected) {} PHP
我将在下一节解释为什么这样做会有问题。
定时攻击是如何工作的?
如果令牌比较的实现是逐个字符比较,并在第一次出错时立即终止,那么第一个字符正确的请求平均需要的时间会稍长一些。
因此,第一个字符正确的请求平均处理时间比第一个字符错误的请求稍长。第二个字符也是如此,依此类推。
攻击者可以利用这一点,测试每个位置的所有可能字符,频繁测量响应时间并进行统计分析。平均响应时间最长的字符可能是正确的。这样,令牌就一步步被重建了。
为什么选择hash_equals()?
PHP 提供了一个恒定时间字符串比较函数hash_equals()。只要两个字符串的长度相同,它就能确保比较的执行时间与通用前缀无关。
安全替代方案(适用于 Laravel、PHP、Wordpress):
$expected = getenv('GITHUB_API_TOKEN'); $provided = $_GET['token'] ?'';if(hash_equals($expected, $provided)) {} PHP
注意:只有当长度相等时,hash_equals()才 "与时间无关"。良好的做法是设计令牌,使其长度恒定(例如 HMAC、固定长度的 UUID 或随机固定长度的 Base64 字符串)。
你知道吗?只有UUID-V4是完全随机生成的,因此常用于安全令牌。由于 UUID-V4 不包含时间或设备相关信息,因此具有高度的不可预测性,可防止基于模式或预测的攻击。这正是 UUID-V4 成为安全、难以猜测的标识符首选的原因。
额外的防御措施--不仅仅是hash_equals()
- 恒定令牌长度
使用固定长度的令牌,如 UUID-V4。如果攻击者猜到了长度,只要字符本身是安全的,就不会有太大影响。
- 速率限制
限制每个 IP 或每个账户的请求。定时攻击需要多次测量;限制速率会大大增加攻击者的工作量和成本。
- 记录和监控
识别异常模式(多次令牌尝试、某些参数的系统性变化)并自动做出反应。
- 盐/HMAC/签名
使用签名令牌(带有秘钥的 HMAC)或 OAuth 令牌,而不是公开比较原始静态密钥。
- TLS/HTTPS
加密可保护通信通道--虽然不能防止对服务器内部的定时攻击,但这是一项基本要求。
- 令牌轮换
令牌的使用期限较短,可减少令牌受损带来的好处。
结论
定时攻击并不是一个纯理论问题--在实践中已经对其进行了研究,并可能造成严重后果,尤其是对可公开访问的端点而言。好消息是:对于 PHP/Laravel/Wordpress 开发人员来说,第一个应对措施非常简单:
- 用
hash_equals()替换敏感字符串的直接比较。
- 确保恒定的令牌长度、速率限制、日志记录和安全令牌管理。