重放攻击,好生猛的家伙!
1.小黑的烦恼
大白和小黑是一对好工友,前阵子小黑跳槽到了一家做电商的头部互联网公司,目前还在试用期,整天战战兢兢搬砖,都累瘦了。
前几天小黑因为一个接口安全问题,差点没过试用期,真是不容易呀。
我们来看看是咋回事:
原来小黑写的接口遇到了重放攻击,让我们一起来帮帮小黑吧!
2.什么是重放攻击
2.1 重放攻击的定义
重放攻击(Replay Attacks)又称重播攻击、回放攻击,是指
攻击者发送一个服务端已接收过的包,来达到欺骗系统的目的
,主要用于身份认证过程,破坏认证的正确性。
重放攻击可以由
发起者
,也可以由拦截并重发该数据的敌方
进行,攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。
重放攻击在任何网络通信过程中都可能发生,是计算机世界黑客
常用的攻击方式之一
。
这个定义我最开始读的时候没太理解,看了几遍才看懂,其实表达了三层意思:
重放攻击就是攻击者把服务端收到过的数据包反复请求,由于是已经收到的合法包,如果服务端防范不当就可以顺利通过身份认证。
重放攻击的攻击者可以是合法的客户端,比如你的外部合作方搞错了循环了100次发相同的数据,还有一种是客户端和服务端交互时被窃取了,由中间人发起了攻击。
重放攻击很普遍,是一种常见的攻击防范,接口安全设计必须要考虑进去。
了解重放攻击概念之后,先看看小黑是如何设计接口的,再看问题在哪里以及解决方案是什么。
2.2小黑的设计方案
小黑设计的接口是为了接入外部数据,接入方比较多,数据推送量也比较大。
小黑是这么设计的:
每一个接入方分配唯一账号标记 data_from字段 每一个接入方分配唯一接入密钥 32位长的secret_key字段 接口中有几个必填字段,按照字典序排列生成字符串seg_str
data_from=abc&data_type=input&data_time=1634365534&interface_name=xyz
计算签名sign值,并转换为16进制小写
md5(seg_str&secret_key=xxxxxx).hex().lower()
接入方按照规则进行进行明文传输,小黑的服务端收到之后将必填字段按照同样的字典序生成seg_str,再拼上对应账号的secret_key,计算出sign值。
小黑根据服务端计算出的sign和请求中带的sign进行对比,如果一致则认为是合法请求,开始正常处理。
这个方案主体是没问题的,和数字证书的验证逻辑很相似,但是还是有一些漏洞。
2.3小黑的方案漏洞
其实这里接入方数据包是否进行对称加密并不能解决重放攻击的问题。
因为重放攻击者完全不关心内容是什么,只是简单的把请求数据重新发一次就可以,这也是重放攻击命名的由来。
考虑重放攻击的特点,我们继续看:
漏洞一
接口没有对请求时间范围的进行限制,比如一些接入方很偷懒每次请求都只传一个固定的data_time,这样无法区分是老请求还是新请求。漏洞二
验证身份只有sign值,即使sign对比是正确的,仍然无法确定就是接入方的合理行为,也就是无法确定该请求是否出现过,有点像接口幂等的感觉。
可能有的朋友会问,重复请求有啥问题吗?
事实上,有的接口重复请求没问题,但是小黑的数据交互接口,重复请求是有问题的,一来给系统负载带来影响,二来数据会被反复在数据库更新,如果明文被篡改,问题会更明显。
综上,结合重放攻击的定义、小黑设计方案、方案漏洞等,我们明确了小黑的接口存在重放攻击风险,并且被攻击了。
那么该如何避免和解决重放攻击呢?
3.重放攻击的解决方案
如果重放攻击是被拦截数据的中间人实施的,那么也可以算一种中间人攻击。
重放攻击一般来说不涉及数据包内容的篡改,因此属于比较简单的中间人攻击,合理使用密文传输可以有效解决密文被篡改的问题,不过本文暂不讨论轻请求包加密传输的问题。
根据重放攻击的特点,解决之道也非常明显:确定请求是否已经存在过,存在过拒绝,新请求则放行
。
3.1方案一:唯一标记 每个请求
这种方案最直白,让请求方增加一个全局唯一标记uniqe_key,服务端收到之后只需要确定uniqe_key是否存在就可以了。
如果uniqe_key已经出现过,就认为是老请求,直接拒绝。 如果uniqe_key没有出现过,就认为是新请求,直接通过。
但是这个方案听着简单,实际操作起来有点费劲,不信你看:
请求端如何生成全局唯一标记,肯定不能单独向服务端请求接口分配,这样就套娃了,鸡生蛋问题,所以需要服务端给一个sdk让请求方离线生成,或者约定生成方案让客户端来生成uniqe_key。
服务端需要保存所有的uniqe_key,并且是持续增长的,考虑这种O(1)查询要求,一般要用NoSQL来实现,对存储要求很高。
所以这种方案的存储和查询成本比较高,请求量大的场景或许行不通。
3.2方案二:严格限制 请求时间
这个方案的意思就是服务端从请求中获取data_time时间戳,如果请求时间戳和服务器时间戳之差超过某个范围,比如60s,就认为是不合法的,可以拒绝。
或许有朋友问,为啥要设置60s的波动呢?
客户端和服务端的时间基准可能不同,这涉及NTP时间同步、时区等场景的存在,客户端和服务端时间很难严格一致。
即使二者时钟同步一致,客户端和服务端的网络交互也需要时间,在ms级别二者不可能完全一致,使用秒级时间戳,也还是可以接受的。
这种方案可行性稍微好一些,但是无法实际上并不能保证客户端时钟和服务端时钟的一致性,同时合理设置波动时间是1分钟还是1小时,也需要大量实践,波动时间设置太大太小都会有影响。
假设设置的时间差容忍度是10分钟,则在10分钟内的重放攻击是无法避免的,超过10分钟则可以直接拒绝,此时方案是有效的。
3.3方案三:时间限制+唯一/随机字符串
虽然方案一和方案二有明显的问题,但是思想却是可以借鉴的。
结合方案一和方案二,就出现了新的方案:
假如设置请求时间和服务器时间的容忍范围为15分钟,超过15分钟的请求直接拒绝 客户端时间和服务端时间差在15分钟之内的情况,则借助于唯一标记或者随机字符串sep_key来进行限制,如果该账号下15分钟内出现过sep_key则直接拒绝,否则认为是新请求,予以通过
随机字符串或者唯一标记的实际方案比较多,有的是借助于已经生成的sign值,有的是增加nonceStr新字段,把这些值按照账号来区分存储在redis增加过期时间就可以了。
这个方案的亮点就在于使用随机字符串或者唯一标记杜绝了时间波动范围内的重放攻击,存储量和生成随机数的方案都很简单。
实际场景中这个方案应用非常多,算是一种有效的手段。
3.4阿里云api网关的解决方案
客户端调用API时,需要在请求中添加计算的签名。API网关在收到请求后会使用同样的方法计算签名,同用户计算的签名进行比较,相同则验证通过,不同则认证失败。
在API网关的签名中,提供X-Ca-Timestamp、X-Ca-Nonce两个可选HEADER,客户端调用API时一起使用这两个参数,可以达到防止重放攻击的目的。
X-Ca-Timestamp
发起请求的时间,可以取自机器的本地实现。当API网关收到请求时,会校验这个参数的有效性,误差不超过15分钟。X-Ca-Nonce
这个是请求的唯一标识,一般使用UUID来标识。API网关收到这个参数后会校验这个参数的有效性,同样的值,15分内只能被使用一次。
综上,小黑在自己方案的基础上加上重放攻击防范,就基本可用了,如果加上对数据的对称加密,以及整个内容的sign计算就可以防范高级别的中间人攻击,对抗篡改内容的恶意情况。
4.小结
本文从一个生产环境的接口安全性设计案例进行展开,先介绍了已有接口方案、接口潜在漏洞,让大家对案例有更深的理解。
进一步介绍了重放攻击常见解决方案和各自优缺点,同时介绍了阿里云api网关的方案,最终顺利帮小黑解决了重放攻击问题。
就聊到这里吧,祝大家周末愉快!
推荐阅读: