一、概念和定义
1、什么是重放攻击?
我们在设计接口的时候,最担心一个接口被别有用心的用户截取后,用于重放攻击。重放攻击是什么呢?就是把请求被原封不动地重复发送,一次,两次...n次。
2、重放攻击造成的后果
一般的请求被提交到后台执行,先会经过【页面验证】后提交给【后台逻辑】,提交给【后台逻辑】过程中请求可能会被被拦截,
- 如果这个【后台逻辑】是插入数据库操作,就很容易造成多条重复的数据插入数据库。
- 如果这个【后台逻辑】是查询数据库操作,就可能导致反复查询,数据库被堵住等情况。
所以,我们需要一种防重放的机制来做请求验证。
二、解决方案
1、客户端(携带timestamp+nonce)
我们常用的防止重放的机制是使用timestamp和nonce来做的重放机制。
1.1、timestamp
timestamp用来表示请求的当前时间戳,这个时间要事先和服务器时间戳校正过。我们预期正常请求带的时间戳会是不同的,如:假设正常人每秒至多会做一个操作。
每个请求携带的时间戳不能和当前时间距离很近,即不能超过规定时间,如60s。这样请求即使被截取了,也只能在有限时间(如:60s)内进行重放,过期就会失效。
1.2、nonce
仅仅提供timestamp还是不够的,我们还是提供给攻击者60s的可攻击时间了。要避免60秒内发生攻击,我们还需要使用一个nonce随机数。
nonce是由客户端根据随机生成的,比如 md5(timestamp+rand(0, 1000),正常情况下,在短时间内(比如60s)连续生成两个相同nonce的情况几乎为0。
2、服务端(验证时间是否超限,检查签名)
服务端第一次在接收到这个nonce的时候做下面行为:
1 去redis中查找是否有key为nonce:{nonce}的string
2 如果没有,则创建这个key,把这个key失效的时间和验证timestamp失效的时间设置一致,比如是60s。
3 如果有,说明这个key在60s内已被使用过了,这个请求就可以判断为重放请求。
3、方案流程
http://www.xxxx.com?userId=123&userName=zhangsan×tamp=1480556543&nonce=43f34f33&sign=80b886d71449cb33355d017893720666
在这个请求中,userId和userName是真正需要传递的业务参数,timestamp,nonce,sign都是为了签名和防重放使用。
timestamp是发送请求时间,nonce是随机串,sign是对uid,timestamp,nonce(对于一些rest风格的api,建议业务参数一起签名)。
服务端接到这个请求的处理逻辑:
- 先验证sign签名是否合理,证明请求参数没有被中途篡改
- 再验证timestamp是否过期,证明请求是在最近60s被发出的
- 最后验证nonce是否已经有了,证明这个请求不是60s内的重放请求