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