受保护的报文不会在密码学上过期,所以五分钟前有效的报文现在依然有效,因为加密和完整性保护对字节何时产生只字未提。能截获并重发流量的攻击者不需要攻破密码。正因如此,每一个认真的隧道协议都在密钥旁携带序列状态。

机制很小,它的失效方式也很容易被误认为丢包。

有效的密文不等于新鲜的密文

认证只回答一个问题:这些字节是否未经改动地来自持有密钥的人。它不回答接收方是否第一次看到它们。重放的报文完美通过第一道检验,这正是单靠完整性检查无法阻止重放、协议需要关于接收的第二条规则的原因。只做认证的接收方,面对握着录音的攻击者没有任何防御。

这条规则实现起来便宜,表述起来也便宜:在给定密钥下只接收一次给定的受保护报文。这一领域其余的一切,都源于实现如何追踪那一次。

把重放丢弃读成解密失败,会让你去查完全错误的子系统。

序列号与窗口

IPsec ESP 携带序列号,接收方维护一个近期已接受值的滑动窗口,ESP 规范描述了默认窗口大小以及扩大它的选项。WireGuard 走另一条路:每把密钥一个计数器,并对其做显式检查。两种设计都不假设报文按序到达,因为真实网络会重排。两者都容忍重排,都不容忍重复。

正是窗口让这种容忍成为可能。略落后于所见最高值的报文,只要之前未被接受,仍可接受;落后很远的,或位置已被标记的,会被丢弃。窗口开得越大,就能容忍更多重排,代价是每条关联的状态更多。

诊断计数器会告诉你处于哪种情形。重放丢弃增加的是特定计数器而非通用错误,读这个计数器就是第一个有用的观察。

nstat -az | grep -i XfrmIn
ip -s xfrm state | head -n 30
wg show wg0 transfer

状态属于密钥,而不属于主机

计数器状态绑定到安全关联或密钥世代,因此换密钥会创建新的上下文和它自己的序列空间。不清除密钥就清空计数器,或手工重置后复用同一关联,都会打破接收窗口赖以存在的假设。这就是为什么存在协议驱动的换密钥,而不是运维人员可见的重置按钮。

手工干预之后隧道行为异常时,先问干预是否动过序列状态。看起来完全相同的配置可能表现不同,因为它继承的状态并不是它期望的状态。

关闭任何东西之前先诊断原因

丢包、重排、重复流量和实现缺陷都表现为重传,而其中只有一种是重放问题。为了压噪音而关掉检查,会移除你原本依赖的性质,也让原始原因更难找,因为此时重传已完全无法解释。

收集范围受限的证据:关联标识符、序列号和计数器,不含载荷。这足以区分重复与重排到达,也不会把你的诊断文件变成用户流量的副本。

检查并不承诺什么

防重放是一个协议内部报文处理的性质。它不意味着运维方不留日志、网站无法识别账号,或会话是匿名的。那是关于不同系统的不同问题,再强的防重放设计也不回答其中任何一个。

带受支持设置的受维护软件,是用户手上实际可用的控制。协议负责报文;其余仍是运维决定。

相关文章