MST
星途 面试题库

面试题:网络编程下在线支付基于HTTPS安全验证时,如何防止重放攻击

在基于HTTPS的在线支付安全验证流程中,重放攻击是一种潜在威胁。请阐述你所知道的防止重放攻击的方法,并说明在网络编程层面如何具体实现这些方法。
44.0万 热度难度
后端开发网络编程

知识考点

AI 面试

面试题答案

一键面试

防止重放攻击的方法

  1. 时间戳:在请求中添加当前时间戳,服务器验证时间戳是否在合理时间范围内。如果时间戳超出一定时间(如几分钟),则判定为无效请求,拒绝处理。这样攻击者无法使用旧的请求进行重放,因为时间戳已经过期。
  2. 随机数(Nonce):每次请求生成一个唯一的随机数,服务器记录已处理过的随机数。当收到新请求时,检查随机数是否已存在,若存在则为重复请求,拒绝处理。随机数可以是一个随机生成的字符串或数字。
  3. 序号(Sequence Number):客户端和服务器约定一个初始序号,每次请求序号递增。服务器根据序号的连续性来判断请求是否合法。如果收到的序号不连续或小于已处理的最大序号,则拒绝请求。

网络编程层面具体实现

  1. 时间戳实现
    • 客户端:在发送请求前,获取当前时间并将其作为时间戳添加到请求参数中。例如在Python中使用time.time()获取当前时间戳(以秒为单位),并添加到HTTP请求的headersbody中。
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;
            }
        }
        // 处理正常支付逻辑
    }
}
  1. 随机数实现
    • 客户端:使用随机数生成函数生成唯一随机数,并添加到请求中。在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小时
    // 处理支付逻辑
});
  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()