Bearer Token 校验的时序攻击漏洞与修复
问题从哪里来
我给支付宝支付系统开发外部 API 时,需要为接口加 Bearer Token 认证。
最直观的做法:用收到的 Token 和环境变量里的 API Key 做字符串比对。匹配就放行,不匹配就返回 401。
刚写完觉得没问题 —— 密钥存在环境变量,攻击者猜不到,逻辑也简单。
直到查阅资料,才发现字符串直接比对本身就是一个攻击面。
这个漏洞叫 时序攻击(Timing Attack)。它和密码学无关,靠的是响应时间的差异。
真正卡住的是哪一步
问题出在大多数语言的字符串比对实现上。
以 JavaScript 为例,=== 逐字符比对,一旦发现不匹配就立即返回 false。不同位置的匹配失败,耗时不同:
// 假设正确 Token 是 "sk-abc123xyz" |
攻击者通过测量响应时间,逐位猜测密钥:
flowchart TD
A[传 a**********] -->|响应快| B[第一位不是 a]
A2[传 s**********] -->|响应稍慢| C[第一位是 s]
C --> D[传 sk*********]
D -->|响应更快| E[第二位不是 k,重试]
E --> F[反复几百次猜出完整密钥]这不是理论攻击。毫秒级延迟测量下,这类攻击有大量真实案例。
我最后怎么处理
Node.js 的 crypto 模块提供 timingSafeEqual,一个常量时间比对函数。
原理很简单:无论哪一位不同,都遍历完整长度。耗时只和 Buffer 长度有关,和内容无关。
修复后的验证逻辑:
const crypto = require('crypto'); |
三个关键点:
- 用 Buffer 而不是字符串:
timingSafeEqual接受 Buffer 参数。 - 长度比较不能短路:长度不同就立刻返回,攻击者能推测密钥长度。这里用
expectedBuf自己比对自己,消耗等量时间。 - 强制常量时间:即使长度不匹配,也执行一次比对。
再加两层额外防护:
- 速率限制:对同一 IP 限频,增加暴力破解时间成本。
- 日志监控:记录 401 频率,频繁失败的 IP 自动进黑名单。
这件事留下的经验
第一,字符串比对和安全比对是两回事。
业务代码里用 === 没问题。但安全敏感上下文(Token、签名、密码校验)里,任何一次直接比对都是在暴露信息。
第二,安全漏洞不一定是逻辑错误。
这个漏洞没有认证逻辑错误,问题在比对算法的物理特性(执行时间)。这类侧信道攻击容易被忽视,因为开发者习惯从」输入输出正确性」验证代码,不会想到」时间也是输出」。
第三,修复成本很低,遗忘成本很高。
核心只改两行代码。但不修,每个暴露 API 的应用都是潜在入口。类似场景还有:API 签名校验、Webhook HMAC 验证、密码哈希比较 —— 比对逻辑都应该走常量时间。
第四,给代码加文档标注。
我在修复处加注释说明这是安全敏感代码,并记录修复方案。防止以后」代码清理」时把 timingSafeEqual 改回 === —— 看起来像优化,实际是退化。
参考资料
- Node.js crypto 文档:https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
- OWASP Timing Attacks:https://owasp.org/www-community/attacks/Timing_Attack