重放攻击:Bob被同一个“转账”消息坑了三次之后……

0 阅读10分钟

加密了也不安全?有一种攻击方式,不需要破解密码,不需要看懂数据,只需要……录下来,再放一遍

一、开篇:Bob的倒霉一天

在密码学和网络安全领域,有两个著名的“老演员”——AliceBob。他们俩几乎出现在每一本教材里,Alice负责发消息,Bob负责收消息,旁边偶尔有个叫Eve的坏蛋负责偷听。

今天,Bob又倒霉了。

事情是这样的:Alice给Bob发了一条消息,内容很简单——“请转账1000美元给Charlie”。

Bob收到消息,核实了一下(嗯,是Alice的签名没错),然后乖乖执行了转账。

一切正常,对吧?

五分钟后,Bob又收到一条消息:“请转账1000美元给Charlie”。Bob心想:Alice可能手抖发了两遍?算了,再转一次吧。

又过了五分钟,同样的消息又来了。第三次。

Bob终于忍不住了,打电话问Alice:“你到底要转几次?”

Alice一脸茫然:“我就发了一次啊。”

那么问题来了——多出来的那几次,是谁发的?

答案是:Eve。她偷偷录下了Alice发的第一条消息,然后隔几分钟就原封不动地重放一次。Bob的服务器根本分不清哪条是Alice刚发的,哪条是Eve在“播放录音”。

这就是今天要聊的主角——重放攻击(Replay Attack)

image.png

二、所以重放攻击到底是啥?

用大白话解释:你说话被录了音,坏人把这段录音原样放给别人听,别人以为是你又在说话。

技术版的说法是:攻击者捕获一段通信数据,在之后某个时间点重新发送给接收方,欺骗接收方认为这是一个新的合法请求。

注意一个关键点:攻击者不需要理解数据内容,更不需要解密

想象一下:你录了一段外星人说话的声音,你完全听不懂,但你把录音放给另一个外星人听,他以为是他的同伴在说话。重放攻击就是这么回事——Eve不需要知道“转账1000美元”是什么意思,她只需要把它原样扔给Bob,Bob自己会执行。

这就是重放攻击最“流氓”的地方:它不破解你的加密,它绕过了加密

image.png


三、重放攻击能干什么?几个真实的“骚操作”

理论说完了,来看看重放攻击在实际中能干出什么“好事”。

场景1:偷你的登录状态

你登录一个网站,输入用户名和密码。浏览器把密码加密后发给服务器,服务器验证通过,让你进去了。

Eve在旁边悄悄录下了这个加密的登录包。

第二天,Eve把录下来的包原样发给服务器。服务器一看:咦?这个加密包昨天验证通过过啊(虽然它不记得了),于是放行。Eve成功登录了你的账号。

这里有个反直觉的点:即使你的密码是全世界最强的、用AES-256加密的,也没用。因为Eve不需要知道密码是什么,她只需要把加密后的那串乱码原样重放一遍。服务器收到的依然是“合法的密文”。

场景2:让你多付钱

你在某电商平台买了一部手机,支付了5000元。支付请求被Eve捕获了。

Eve在当天晚上,把这个支付请求重放了10次。

如果你的支付系统没有防重放机制,后果就是:你的银行卡被扣了10次5000元,而你只收到了一部手机。

这就是为什么银行类App的每个支付请求都会带一个一次性令牌——用一次就作废,谁也别想重放。

场景3:偷车(老式遥控钥匙)

在早期的汽车遥控钥匙系统中,你按下解锁键,钥匙会发射一个固定的无线电信号,车门收到信号就解锁。

Eve拿一个几十块钱的SDR(软件定义无线电)设备,在你锁车的时候录下这个信号。

你离开停车场后,Eve把录下的信号重放一遍——车门开了,东西拿走,干干净净。

现在的车为什么不怕了? 现代车钥匙使用**滚动码(Rolling Code)**技术——每次解锁信号都不一样,上一次用的码下一次就失效了。这就是专门防重放攻击的设计。

image.png

场景4:欺骗传感器

这个场景有点“工业风”,但很有意思。

假设一个工厂里有温度传感器,每隔10秒向监控系统上报一次温度数据。Eve截获了一个数值为“25°C”的正常数据包。

然后,真实温度因为故障开始飙升——50°C、70°C、90°C……

但Eve一直在重放那个“25°C”的旧数据包。监控系统屏幕上,温度曲线始终是一条漂亮的直线,显示一切正常。

而真实的工厂,可能已经快着火了。

这不是科幻情节,在工业控制系统安全研究中,这种攻击已经被多次演示过。

image.png


四、为什么加密了也没用?

看到这里,你可能已经意识到了一个问题:

我明明用了HTTPS、用了AES加密、用了各种安全协议,为什么重放攻击还是能生效?

答案很简单:加密解决的是“看不懂”的问题,重放攻击利用的是“看起来一样”的问题。

我们来打个比方:

  • 加密的作用是:把你的信放进一个只有收信人能打开的保险箱里,路上的人看不到信的内容。
  • 重放攻击的做法是:不打开保险箱,直接把整个保险箱原样寄出去。收信人打开一看,信还是那封信,于是照做。

换句话说,Eve根本不需要知道你的密码、你的银行卡号、你的任何隐私信息。她只需要完整地复制你发送的那段数据,然后重新发给服务器。

服务器面对这段数据时,会怎么想?

  • 格式正确 ✅
  • 签名有效 ✅
  • 加密方式正确 ✅

服务器没有任何理由拒绝它。因为从技术角度看,这确实是一个“合法”的请求。

唯一的问题是:这个请求不新鲜。它是5分钟前、或者5个小时前的那条请求,被重新播放了一遍。

而你的服务器,恰恰没有检查“新鲜度”。


五、怎么防?说人话版

防重放攻击的核心思想很简单:让每个请求都有唯一标识,或者有过期时间。

翻译成人话就是:让服务器能认出“这个请求我见过”或者“这个请求太老了”。

下面是几种主流的方法,我尽量用人话讲清楚。

方法1:贴时间标签(Timestamp)

每个消息在发送时带上当前时间戳。接收方收到后,看看这个时间戳和当前时间的差距。

  • 如果差距在允许范围内(比如5秒内),认为是新鲜消息,放行。
  • 如果差距太大(比如你发了一个昨天的时间戳),直接丢弃。

缺点:通信双方的时间需要大致同步。不过现在的设备基本都通过NTP自动对时了,这个问题不大。

image.png

方法2:发号施令(序列号)

每个消息分配一个递增的序号:1、2、3、4……

接收方维护一个“最后收到的序号”。如果收到的新消息序号小于等于这个序号,说明是重复的(可能是重放),直接丢弃。

例子:你已经收到过5号消息,现在又来了一个5号消息——明显有问题,扔掉。

IPsec协议使用的“滑动窗口”技术就是这个思路的升级版,它允许少量乱序,但坚决拒绝重复。

方法3:发一次性的暗号(Nonce)

Nonce = Number used once,意为“只用一次的数字”。

客户端每次请求时带一个随机生成的Nonce值,服务器把所有收到的Nonce存起来。如果同一个Nonce出现第二次,拒绝。

优点:理论上最安全。
缺点:服务器要记住所有用过的Nonce,时间长了存储压力大。解决方案是给Nonce加一个有效期,过期了就忘了。

image.png

方法4:限制请求频率(Rate Limiting)

这个方法简单粗暴:同一个用户、同一个接口、短时间内只允许请求一次。

比如:用户请求了“发送验证码”,5秒内再来一次就直接拒绝。

这虽然不能完全防止重放(如果攻击者等5秒再放呢?),但在很多场景下已经足够有效,而且实现成本极低。


实际项目中,通常是几种方法组合使用。比如:时间戳 + Nonce + Redis缓存,这是目前Web API防重放的常见组合拳。


六、重放攻击 vs 中间人攻击:别搞混了

很多人把重放攻击和中间人攻击搞混,这里快速区分一下。

重放攻击中间人攻击
怎么干的录下来,回头再放在线拦截、修改、转发
要不要实时在线不需要,可以隔几天再放必须在通信过程中实时介入
能不能改内容基本不能(改了可能露馅)可以随意修改
防御重点防重复、防陈旧加密 + 身份认证

一句话总结

  • 重放攻击像个录音机——只播放,不修改。
  • 中间人攻击像个二传手——截下来、改一改、再传过去。

七、一个真实小故事

最后讲个真实案例,让你知道重放攻击不是理论上的“纸老虎”。

2015年左右,安全研究人员测试了市面上几款便宜的智能门锁。他们发现其中一款门锁的无线开锁信号是固定的——也就是说,你每次按下开锁键,发射出去的信号内容一模一样。

研究人员花了不到20块钱买了一个SDR接收器,在门口蹲了五分钟,录下了房主开锁的信号。然后他们走到一边,把录好的信号重放了一次。

门开了。

这个漏洞后来被修复了,但这个故事告诉我们一个道理:不要假设“没人会这么干”。重放攻击的成本往往低得离谱,而后果可能高得吓人。

类似的案例还有:

  • 早期某些游戏的“刷金币”漏洞——重放获取金币的请求包
  • 某银行App的登录重放——重放登录包可以拿到新的会话Token
  • CAN总线汽车攻击——重放刹车、转向指令 image.png

八、结尾:一句话总结 + 给你的建议

一句话总结全文

重放攻击不破解你的密码,不修改你的数据,它只是把你的“录音”再放一遍。防它的关键,就是让每个请求都有“新鲜感”——要么带上时间戳,要么带上序号,要么带上一次性暗号。

如果你是普通用户:

  • 你不需要太担心——现代主流协议(HTTPS、SSH、IPsec)都有防重放机制
  • 车钥匙、智能锁尽量选知名品牌(它们会用滚动码技术)
  • 保持软件更新,厂商会修复这类漏洞

如果你是开发者(这段可能更重要):

  • 别以为上了HTTPS就万事大吉——HTTPS防的是窃听和篡改,不完全防重放
  • 关键接口(支付、修改密码、转账)必须加防重放机制
  • 最简单的起步方案:时间戳 + 签名 + 短时间窗口
  • 更完善的方案:Nonce + Redis缓存,或者使用框架自带的防重放功能
  • 尽量设计幂等接口——同一个请求执行多次和执行一次效果相同,至少不会出大问题

最后皮一句

别让你的系统成为“复读机”——同一个请求听多少遍都当第一遍处理,那离被坑就不远了。