重放攻击 Replay Attack

Introduction

重放攻击(Replay attack,或重送攻击)是一种网络攻击,指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。

这种攻击会不断恶意或欺诈性地重复一个有效的数据传输,重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。从这个解释上理解,加密可以有效防止会话劫持,但是却防止不了重放攻击。重放攻击任何网络通讯过程中都可能发生。


How to defend against it

时间戳

首先我们认为一次HTTP请求从发出到到达服务器的时间是不会超过60s的,当你发送一个请求时必须携带一个时间戳timestamp。假设值为10,当请求到达服务器之后,服务器会取出当前时间,假设为t2=80,很明显t2 - timestamp > 60s,那么服务器就认为请求不合法。

发送的数据必须带上sign(比如,MD5签名生成的),避免数据被篡改。

存在的问题是:如果黑客在60s内发起攻击,那么我们就束手无策了。那是不是缩小时间范围就可以解决了?

时间戳 + 随机数nonce

为了避免上面时间戳的问题,可以加入一个随机数nonce,每次成功请求,服务器会保存当前成功请求的随机数nonce,比如存放在redis和数据库中,当请求再次进到服务器,先验证时间戳是否有效,如果有效,再判断携带的随机数nonce是否在缓存或者数据库中已经存在,如果存在,则认为请求非法。

但你会发现,如果系统请求非常多,这个存放nonce的redis或者数据库势必会越来越大,因此,我们只需要保存服务器当前时间60秒内的nonce值即可。

前提条件:保证随机数nonce绝对唯一。

序号

通信双方必须事先协商一个初始序列号,并协商递增方法。然后双方通过消息中的序列号来判断消息的新鲜性。例如,如果nonce=1000已经请求过了,那么nonce<1000的请求都是无效请求。

A single account may have multiple API keys provisioned. In this document, we’ll refer to these as “sessions”. All orders will be recorded with the session that created them. The nonce associated with a request needs to be increasing with respect to the session that the nonce is used on.
This allows multithreaded or distributed trading systems to place orders independently of each other, without needing to synchronize clocks to avoid race conditions.
In addition, some operations (such as Cancel All Session Orders) act on the orders associated with a specific session.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const request = require('request')
const crypto = require('crypto')

const apiKey = '<Your API key here>'
const apiSecret = '<Your API secret here>'
const baseUrl = 'https://api.xxx.com'

const url = '/v1/account'
const nonce = Date.now().toString()
const completeURL = baseUrl + url
const body = {
request: url,
nonce
}
const payload = new Buffer(JSON.stringify(body))
.toString('base64')

const signature = crypto
.createHmac('sha384', apiSecret)
.update(payload)
.digest('hex')

const options = {
url: completeURL,
headers: {
'X-APIKEY': apiKey,
'X-PAYLOAD': payload,
'X-SIGNATURE': signature
},
body: JSON.stringify(body)
}

return request.post(
options,
function(error, response, body) {
console.log('response:', JSON.stringify(body, 0, 2))
}
)

Reference

https://blog.csdn.net/heluan123132/article/details/74375304
https://juejin.im/post/5ad43b86f265da239236cedc
https://docs.gemini.com/rest-api/#private-api-invocation