SEC01 - Replay attack và các phòng chống

  • Đăng bởi JP
  • 14/09/2024

Reply attack (tấn công phát lại) là một loại tấn công mạng trong đó kẻ tấn công chặn và tái sử dụng một gói dữ liệu đã được gửi trong một phiên giao dịch hợp lệ trước đó để lừa hệ thống thực hiện lại hành động mà gói dữ liệu đó yêu cầu. Kẻ tấn công không cần phải giải mã hay hiểu nội dung của gói tin, mà chỉ cần phát lại nó để đạt được mục đích.

Ví dụ về tấn công Reply attack:

  1. Thanh toán trực tuyến: Kẻ tấn công chặn gói dữ liệu khi một người dùng gửi yêu cầu thanh toán (gồm thông tin về tài khoản, số tiền, và các dữ liệu xác thực khác) tới server. Sau đó, kẻ tấn công phát lại yêu cầu đó đến server, khiến server thực hiện một lần thanh toán khác mà không cần sự đồng ý của người dùng.

  2. Đăng nhập: Kẻ tấn công chặn yêu cầu đăng nhập từ người dùng hợp lệ và phát lại yêu cầu này để có thể truy cập vào hệ thống mà không cần biết mật khẩu hoặc thông tin xác thực.

Quá trình của một cuộc tấn công Reply attack:

  1. Chặn gói tin: Kẻ tấn công lắng nghe mạng (thường thông qua một kỹ thuật như “man-in-the-middle”) để chặn một gói tin hợp lệ từ một phiên giao dịch.
  2. Tái sử dụng gói tin: Sau đó, kẻ tấn công gửi lại gói tin này đến server mà không cần thay đổi bất kỳ dữ liệu nào, nhưng yêu cầu đó được server coi là một yêu cầu hợp lệ.
Replay attack

Replay attack

Các phương pháp chống lại reply attack

Dưới đây là tổng hợp các cách chống lại reply attack, mình dùng client bằng JavaScriptserver bằng Python để minh họa. Các phương pháp chủ yếu xoay quanh việc đảm bảo mỗi yêu cầu từ client đến server là duy nhất và không thể bị sử dụng lại.

1. Sử dụng Timestamp (Dấu thời gian)

Nguyên tắc: Mỗi request sẽ chứa một timestamp và server sẽ kiểm tra xem request đó có cũ hay không.

Client (JavaScript):

// Client-side JavaScript
function sendRequest(data) {
    const timestamp = Date.now(); // Get current timestamp in milliseconds
    const payload = {
        data: data,
        timestamp: timestamp
    };

    fetch('/api/endpoint', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
    })
    .then(response => response.json())
    .then(data => console.log('Response:', data))
    .catch(error => console.error('Error:', error));
}

Server (Python):

from flask import Flask, request, jsonify
import time

app = Flask(__name__)
TIME_THRESHOLD = 60  # Time threshold in seconds

@app.route('/api/endpoint', methods=['POST'])
def handle_request():
    data = request.get_json()
    client_timestamp = data.get('timestamp')
    current_time = time.time() * 1000  # Current time in milliseconds

    # Check if the timestamp is within the acceptable threshold
    if abs(current_time - client_timestamp) > TIME_THRESHOLD * 1000:
        return jsonify({'error': 'Request is too old'}), 400

    # Process the valid request
    return jsonify({'message': 'Request is valid'})

Giải thích:

  • Client sẽ gửi đi một timestamp hiện tại khi tạo request.
  • Server sẽ so sánh thời gian nhận request và thời gian trong timestamp, nếu khoảng cách quá xa (giả sử lớn hơn 60 giây), server sẽ từ chối.

Đến đây có bạn lại thắc mắc là vậy trong khoảng thời gian 60 giây hacker vẫn có thể tấn công được? Chúng ta cùng đến giải pháp tiếp theo.

2. Sử dụng Nonce (Số ngẫu nhiên duy nhất)

Nguyên tắc: Nonce là một giá trị ngẫu nhiên chỉ sử dụng một lần. Mỗi request sẽ chứa một nonce để đảm bảo tính duy nhất.

Client (JavaScript):

// Function to generate a random nonce
function generateNonce() {
    return Math.random().toString(36).substring(2);
}

function sendRequestWithNonce(data) {
    const nonce = generateNonce();
    const payload = {
        data: data,
        nonce: nonce
    };

    fetch('/api/nonce-endpoint', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
    })
    .then(response => response.json())
    .then(data => console.log('Response:', data))
    .catch(error => console.error('Error:', error));
}

Server (Python):

from flask import Flask, request, jsonify

app = Flask(__name__)
used_nonces = set()

@app.route('/api/nonce-endpoint', methods=['POST'])
def handle_nonce_request():
    data = request.get_json()
    nonce = data.get('nonce')

    # Check if the nonce has been used before
    if nonce in used_nonces:
        return jsonify({'error': 'Nonce has already been used'}), 400

    # Save the nonce to prevent future reuse
    used_nonces.add(nonce)

    # Process the valid request
    return jsonify({'message': 'Request with nonce is valid'})

Giải thích:

  • Client sẽ tạo ra một nonce ngẫu nhiên và gửi cùng với request.
  • Server lưu trữ tất cả các nonce đã sử dụng để đảm bảo rằng không có request nào bị dùng lại. Thông thường sử dụng redis để lưu trữ.

3. Sử dụng HMAC (Hash-based Message Authentication Code)

Nguyên tắc: HMAC sử dụng một khóa bí mật chung để tạo mã hóa cho nội dung request. Server có thể xác minh tính toàn vẹn của request bằng cách tính lại HMAC và so sánh với kết quả từ client.

Client (JavaScript):

// Function to create HMAC signature
function generateHMAC(data, secret) {
    return CryptoJS.HmacSHA256(JSON.stringify(data), secret).toString();
}

function sendRequestWithHMAC(data) {
    const secretKey = 'shared-secret-key';
    const hmacSignature = generateHMAC(data, secretKey);

    const payload = {
        data: data,
        signature: hmacSignature
    };

    fetch('/api/hmac-endpoint', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
    })
    .then(response => response.json())
    .then(data => console.log('Response:', data))
    .catch(error => console.error('Error:', error));
}

Server (Python):

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
SECRET_KEY = b'shared-secret-key'

def verify_hmac(data, signature):
    calculated_signature = hmac.new(SECRET_KEY, msg=data.encode(), digestmod=hashlib.sha256).hexdigest()
    return hmac.compare_digest(calculated_signature, signature)

@app.route('/api/hmac-endpoint', methods=['POST'])
def handle_hmac_request():
    data = request.get_json()
    message = data.get('data')
    signature = data.get('signature')

    # Verify HMAC signature
    if not verify_hmac(message, signature):
        return jsonify({'error': 'Invalid HMAC signature'}), 400

    # Process the valid request
    return jsonify({'message': 'HMAC verification successful'})

Giải thích:

  • Client sử dụng khóa bí mật chung để tạo HMAC dựa trên nội dung request.
  • Server tính toán lại HMAC từ dữ liệu nhận được và so sánh với HMAC từ client để xác minh tính hợp lệ của request.

4. Sử dụng JWT (JSON Web Token)

Nguyên tắc: JWT là một tiêu chuẩn mã hóa thông tin trong JSON và ký tên bằng một khóa bí mật. Server có thể kiểm tra tính hợp lệ của JWT mà không cần lưu trữ trạng thái session.

Client (JavaScript):

function sendRequestWithJWT(data, token) {
    const payload = {
        data: data,
        token: token
    };

    fetch('/api/jwt-endpoint', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
    })
    .then(response => response.json())
    .then(data => console.log('Response:', data))
    .catch(error => console.error('Error:', error));
}

Server (Python):

from flask import Flask, request, jsonify
import jwt

app = Flask(__name__)
SECRET_KEY = 'shared-secret-key'

@app.route('/api/jwt-endpoint', methods=['POST'])
def handle_jwt_request():
    data = request.get_json()
    token = data.get('token')

    try:
        # Decode JWT token
        decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return jsonify({'message': 'JWT verification successful', 'data': decoded})
    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'Token has expired'}), 400
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Invalid token'}), 400

Giải thích:

  • JWT chứa payload được ký bằng khóa bí mật và server có thể xác minh tính hợp lệ của token mà không cần lưu trữ trạng thái.

Tổng kết:

  • Sử dụng timestampnonce là cách dễ dàng và phổ biến để chống lại reply attack.
  • HMACJWT cung cấp một lớp bảo mật cao hơn khi có khả năng xác minh tính toàn vẹn và tính xác thực của dữ liệu.

Trong các dự án thực tế các bạn nên sử dụng kết hợp các phương pháp trên để tăng hiệu quả bảo mật.

P/S: trong phương pháp dùng HMAC, secret_key được chia sẻ và lưu trữ tại client điều này rất không bảo mật. Vậy chúng ta khắc phục như thế nào, các bạn cùng thảo luận nhé.


Trân trọng,
JP

Thảo luận trên tinh thần học hỏi