最近整理文档,发现之前共享充电宝项目物联网服务记录了接入某厂家设备遇到的问题,里面提到了「重放攻击」。

对接过程中发生了很多事,有些事已经分析解决了,有些问题依然还存在。

xx 需要 http 80 端口通讯(5 个 http 借口),而业务端下发指令的请求也是到网关服务,使用的也是 80 端口。之前 xx 就出现过网关的 80 端口受到 重放攻击(Replay Attacks),这攻击主要是捕捉历史发送到网关服务的指令,然后再重新释放到网关服务。这会导致一些重要接口,如强制弹出、租借出现重复弹宝的情况,如果之前弹出的孔位恰好存在归还,那么就又可能出现无指令弹宝、丢宝的恶性事件。

针对业务端和物联网端通讯,之前有过一些处理,比如增加签名、验签的环节,但这个无法摆脱重放攻击。比较有效的措施,是限制业务端下发指令的服务地址。因为业务服务器就那么几台,所以可以通过安全组配置实现,之前也确实行之有效。现在因为设备也要通过 80 端口通讯(严重怀疑是 xx 自己增加了通讯环节),所以限制服务地址的方法是不行的。之前想过拆分接口,将比较重要的指令下发接口单独放到一个服务中,再做服务地址限制,但这样继续拆下去,网关也要变成两个了,对于将来扩展不太友好。

好在还有一个比较简单的手段可以做一些屏蔽。对照物联网端收到的真假指令发现,两者之间间隔了将近 2 分钟,也就是说攻击方存储并重发也是需要时间的,这样可以对请求到达的时间做限制,超过 5s 或者 10s 则默认指令已失效,这样可以屏蔽掉重放攻击的请求。

另一个比较突出的问题是,物联网在将设备上报的消息解析之后转发给业务端时,经常性会出现报错的情况。项目使用了 restTemplete 来发起服务端的请求,主要是 postForEntity 或者 getForEntity 方法。修改了 http 请求的超时时间和最长传输时长,似乎有用,但失败的情况依然存在。后续需要测试使用其他方法,或者修改参数来达到不丢失消息的预期。

维基百科定义:

重放攻擊是一種惡意或欺詐的重複或延遲有效資料的網路攻擊形式。 這可以由發起者或由攔截資料並重新傳輸資料的對手來執行。這是「中間人攻擊」的一個較低階別版本。 這種攻擊的另一種描述是: 「從不同上下文將訊息重播到安全協定的預期上下文,從而欺騙其他參與者,致使他們誤以為已經成功完成了協定執行。」

怪不得我一开始就觉得像是「中间人攻击」,后面同事说是“重放”,这词听着还觉得很陌生,其实是「中间人攻击」的一种。

如何有效防止API的重放攻击? 提到了加密、签名本身都阻止不了重放攻击。因为加密和签名主要是防止消息内容被修改,直接使用截获的内容重新高频率发送请求,还是可以攻击到。

文档提到了两个常用到的 Timestamp(发起请求的时间) 和 Nonce(请求的唯一标识),将所有字段都加入到签名计算中,先验签,确保内容没有修改;之后再通过 Timestamp 和 Nonce 判断请求是否超时和是否重复。

说说API的防重放机制 提供了使用 md5 签名算法的来进行防重放攻击的示例。

结合微信、支付宝支付接口的例子来看,实现逻辑也大概是这样的,最多再加一层 RSA 可逆加密、解密(微信支付成功回调)。流程上就是先解密关联数据,然后按照文档上要求对所需字段进行排序、签名,再验签。

到了这一步之后,微信、支付宝就没有再做过多要求了。回想起来确实存在漏洞,微信和支付宝要求添加服务器的白名单,到了反过来服务器接受回调通知的时候,也需要验证请求是否来自微信、支付宝。

加密、签名是可以证明消息内容没错,但不能证明消息的发送者身份。唯一能直接证明的就是请求域名或 ip 白名单。间接一点的,就是通过上面提到的,timestamp 和 nonce 对请求的时效性进行判断。

并且,异步回调通知是可能会多次发送给商户系统的

微信支付成功回调通知 API 注意事项
微信支付成功回调通知 API 通知规则
支付宝异步通知说明 验签通过后说明
支付宝异步通知说明 通知特性

支付回调要正确回复之后,才不会重复通知。但意外总是会出现,通知可能会收到一次,或者一次都收不到,还可能收到很多次。

也就是说,除了应对网络环境中的重放攻击以外,还需要处理、过滤这种特定场景下的正常重复通知

业务端遇到过这种情况,本来一开始的业务是不存在问题的,正常的支付成功后,更新订单状态。即使重复收到,也不过是重复更新状态而已。但后面增加了代理商分成的部分逻辑之后,没有意识到通知会重复发送,问题就大了。突然有一天业务端发出报警说,某代理商的账户余额十分不正常,订单规模与他的收益不成比例。然后查看日志发现,一个订单重复分成了很多次。现在已经不能确定,是支付平台没有收到应答,还是服务器收到了「重放攻击」。但教训深刻,损失巨大,虽然后面及时的处理并冻结了有问题的账户,重新确认分账情况,修复了漏洞,挽回了大部分损失,但当时反响还是很大,差点被开掉。当然开掉还是小事,就怕公司再追责,那是真的有点赔不起,毕竟工资才几个钱。

然后就培养了程序员的一个安全意识:不信任任何人,不信任任何其他来源的数据。前端会犯错,数据可能是有问题的,或者假的;第三方服务也是,不要看着平台大就安全,他们考虑的安全通常只包含他们自己。

综上,对于「重放攻击」的防护(尤其是三方平台的回调通知),有三大安全要素需要考虑:

  • 第一,就是三方平台回调,请求地址的域名或 ip 白名单(如果可以的话,因为这需要维护,域名或 ip 可能后期会有变动)。
  • 另一个是要做好解密、验签和时效性校验,排除掉非法请求;
  • 此外还要做逻辑过滤,过滤已处理的请求,以防止对方是个老六,它自己会重复发请求。