防止重放攻击的方法
- 时间戳:在请求中添加当前时间戳,服务器验证时间戳是否在合理时间范围内。如果时间戳超出一定时间(如几分钟),则判定为无效请求,拒绝处理。这样攻击者无法使用旧的请求进行重放,因为时间戳已经过期。
- 随机数(Nonce):每次请求生成一个唯一的随机数,服务器记录已处理过的随机数。当收到新请求时,检查随机数是否已存在,若存在则为重复请求,拒绝处理。随机数可以是一个随机生成的字符串或数字。
- 序号(Sequence Number):客户端和服务器约定一个初始序号,每次请求序号递增。服务器根据序号的连续性来判断请求是否合法。如果收到的序号不连续或小于已处理的最大序号,则拒绝请求。
网络编程层面具体实现
- 时间戳实现
- 客户端:在发送请求前,获取当前时间并将其作为时间戳添加到请求参数中。例如在Python中使用
time.time()
获取当前时间戳(以秒为单位),并添加到HTTP请求的headers
或body
中。
import time
import requests
timestamp = time.time()
headers = {'timestamp': str(timestamp)}
response = requests.post('https://payment.example.com', headers = headers)
- **服务器**:在接收到请求后,提取时间戳,与当前时间对比。如果差值超过设定的阈值(如180秒),则拒绝请求。以Java为例:
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
public class PaymentServlet extends HttpServlet {
private static final long MAX_TIME_DIFFERENCE = 180000; // 3分钟
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
String timestampStr = request.getHeader("timestamp");
if (timestampStr != null) {
long timestamp = Long.parseLong(timestampStr);
long currentTime = new Date().getTime();
if (currentTime - timestamp > MAX_TIME_DIFFERENCE) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Timestamp expired");
return;
}
}
// 处理正常支付逻辑
}
}
- 随机数实现
- 客户端:使用随机数生成函数生成唯一随机数,并添加到请求中。在JavaScript中可以使用
crypto.randomUUID()
生成UUID作为随机数:
fetch('https://payment.example.com', {
method: 'POST',
headers: {
'nonce': crypto.randomUUID()
}
})
- **服务器**:维护一个已处理随机数的存储(如数据库表或内存中的Set)。每次收到请求,检查随机数是否已存在。以Node.js和Redis为例:
const express = require('express');
const redis = require('redis');
const app = express();
const client = redis.createClient();
app.post('/payment', async (req, res) => {
const nonce = req.headers.nonce;
const exists = await new Promise((resolve, reject) => {
client.exists(nonce, (err, reply) => {
if (err) reject(err);
resolve(reply);
});
});
if (exists) {
res.status(400).send('Duplicate nonce');
return;
}
client.setex(nonce, 3600, 'used'); // 设置随机数有效期1小时
// 处理支付逻辑
});
- 序号实现
- 客户端:维护一个本地序号变量,每次请求前递增序号,并将其添加到请求中。以C#为例:
private static int sequenceNumber = 0;
var httpClient = new HttpClient();
sequenceNumber++;
var request = new HttpRequestMessage(HttpMethod.Post, "https://payment.example.com");
request.Headers.Add("sequence-number", sequenceNumber.ToString());
var response = await httpClient.SendAsync(request);
- **服务器**:存储当前已处理的最大序号。收到请求后,检查序号是否大于已处理的最大序号。如果是,则更新最大序号并处理请求;否则拒绝请求。以Python和SQLite为例:
import sqlite3
import web
urls = ('/payment', 'Payment')
app = web.application(urls, globals())
class Payment:
def POST(self):
data = web.input()
seq_num = int(data.get('sequence-number'))
conn = sqlite3.connect('payments.db')
cursor = conn.cursor()
cursor.execute('SELECT MAX(sequence_number) FROM payments')
result = cursor.fetchone()[0]
if result is None or seq_num > result:
cursor.execute('INSERT INTO payments (sequence_number) VALUES (?)', (seq_num,))
conn.commit()
# 处理支付逻辑
return 'Payment processed'
else:
return 'Invalid sequence number'
conn.close()
if __name__ == "__main__":
app.run()