什么是重放攻击?
我们在设计接口的时候,最怕一个接口被用户截取用于重放攻击。重放攻击是什么呢?就是把你的请求原封不动地再发送一次,两次...n次,一般正常的请求都会通过验证进入到正常逻辑中,如果这个正常逻辑是插入数据库操作,那么一旦插入数据库的语句写的不好,就有可能出现多条重复的数据。一旦是比较慢的查询操作,就可能导致数据库堵住等情况,如果是付款接口,或者购买接口就会造成损失,因此需要采用防重放的机制来做请求验证。
HTTPS数据加密是否可以防止重放攻击?
否,加密可以有效防止明文数据被监听,但是却防止不了重放攻击。
如何防重放攻击?答案:客户端在请求参数加上 时间戳 + 随机数
- 时间戳(timestamp)。以服务端当前时间为准,服务端要求客户端发过来的时间戳,必须是最近60秒内(假设值)的。这样,这个请求即使被截取了,你也只能在60s内进行重放攻击。
-
随机数(nonce)。但是,攻击者还有60s的攻击时间呢!所以我们需要在客户端请求中再加上一个随机数(中间黑客不可能自己修改随机数,因为有参数签名的校验呢),服务端会对一分钟内请求的随机数进行检查,如果有两个相同的,基本可以判定为重放攻击。因为正常情况下,在短时间内(比如60s)连续生成两个相同nonce的情况几乎为0。
PS:我们假设这个数足够随机,比如 md5(timestamp+rand(0, 1000)); 也可以使用UUID。
服务端如何校验
服务端第一次在接收到这个nonce的时候做下面行为:
- 去redis中查找是否有key为nonce:{nonce}的string
- 如果没有,则创建这个key,把这个key失效的时间和验证timestamp失效的时间一致,比如是60s。
- 如果有,说明这个key在60s内已经被使用了,那么这个请求就可以判断为重放请求。
校验示例:
请求:http://a.com/?uid=123×tamp=1480556543&nonce=43f34f33&sign=80b886d71449cb33355d017893720666
这个请求中,
- uid是我们真正需要传递的有实际意义的参数,
- timestamp、nonce、sign都是为了签名和防重放使用。
- timestamp是发送接口的时间,nonce是随机串,sign是对uid、timestamp、nonce的签名。签名的方法可以是md5({秘要}key1=val1&key2=val2&key3=val3...)
服务端接到这个请求:
- 先验证sign签名是否合理,证明请求参数没有被中途篡改
- 再验证timestamp是否过期,证明请求是在最近60s被发出的
- 最后验证nonce是否已经有了,证明这个请求不是60s内的重放请求
web层面也可以采用在页面中加入token方式、手机验证码、滑动验证码等方式来防止攻击。
防御手段
防止重放攻击的方法是使用不重数
1.加随机数
- 优点是认证双方不需要时间同步,双方记住使用过的随机数,如发现报文中有以前使用过的随机数,就认为是重放攻击。// ------“双方” 意思是客户端、服务端都需要做防重放。但现状是一般只对客户端要求。
- 缺点是需要额外保存使用过的随机数,若记录的时间段较长,则保存和查询的开销较大。
2. 加时间戳
- 方法优点是不用额外保存其他信息。
- 缺点是认证双方需要准确的时间同步,同步越好,受攻击的可能性就越小。但当系统很庞大,跨越的区域较广时,要做到精确的时间同步并不是很容易。
private readonly string TimeStamp = ConfigurationManager.AppSettings["TimeStamp"];//配置时间戳 [HttpPost] public ActionResult TestApi() { string RequestTime = Request["rtime"]; //请求时间经过RSA签名 try { //请求时间RSA解密后加上时间戳的时间即该请求的有效时间 DateTime Requestdt = DateTime.Parse(RSACryptoProvider.Decrypt(RequestTime, RSA_Keys.Private)).AddMinutes(int.Parse(TimeStamp)); DateTime Newdt = DateTime.Now; //服务器接收请求的当前时间 //if 请求的有效时间 < 现在服务器接受请求的时间 即该请求失效 if (Requestdt < Newdt) { return Json(new { success = false, message = "该请求已经失效" }); } else { //进行其他操作 } } catch (Exception ex) { return Json(new { success = false, message = "请求参数不和要求" }); } }
3. 加流水号
就是双方在报文中添加一个逐步递增的整数,只要接收到一个不连续的流水号报文(太大或太小),就认定有重放威胁。
- 该方法优点是不需要时间同步,保存的信息量比随机数方式小。
- 缺点是一旦攻击者对报文解密成功,就可以获得流水号,从而每次将流水号递增欺骗认证端。
在实际中,常将方法(1)和方法(2)组合使用,这样就只需保存某个很短时间段内的所有随机数,而且时间戳的同步也不需要太精确。
对付重放攻击除了使用本以上方法外,还可以使用挑战一应答机制和一次性口令机制,而且似乎后面两种方法在实际中使用得更广泛。