PHP/포스팅

[PHP] CSRF 공격 방지 데이터 베이스 활용

짜집퍼박사(짜박) 2024. 12. 26. 01:01

CSRF(크로스 사이트 요청 위조) 방지를 구현할 때 데이터베이스를 사용하는 방식은 CSRF 토큰을 세션 기반으로 저장하는 것 대신, 데이터베이스에 저장하여 토큰을 더욱 체계적으로 관리하는 방법입니다.

 

1. CSRF 방지 데이터베이스 방식 개요

1.1 데이터베이스를 사용하는 이유

(1) 확장성: 대규모 시스템에서 토큰 관리를 일원화 가능.

(2) 추적 가능성: 누가, 언제, 어떤 요청에서 토큰을 사용했는지 기록 가능.

(3) 다중 세션 관리: 사용자가 여러 디바이스에서 로그인했을 때 별도 관리 가능.

 

1.2 작동 원리

(1) 사용자가 폼을 요청하면, CSRF 토큰을 생성하고 데이터베이스에 저장.

(2) 사용자에게는 CSRF 토큰을 폼에 포함하여 전송.

(3) 폼 제출 시, 서버는 데이터베이스에서 해당 토큰의 유효성을 검증.

(4) 유효성 검증 후 요청을 처리하고, 검증된 토큰은 삭제(또는 만료 처리).

 

2. 데이터베이스 테이블 설계

CREATE TABLE csrf_tokens (
    id INT AUTO_INCREMENT PRIMARY KEY,
    token VARCHAR(64) NOT NULL UNIQUE,
    user_id INT NOT NULL, -- 사용자의 고유 ID
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NOT NULL
);

- id: 고유 ID.

- token: 생성된 CSRF 토큰 (64자리 고유 문자열).

- user_id: 해당 토큰과 연결된 사용자 ID.

- created_at: 토큰 생성 시간.

- expires_at: 토큰 만료 시간.

 

3. 실무 코드

3.1 CSRF 토큰 생성 및 저장

csrf.php

<?php
require 'db.php';

/**
 * CSRF 토큰 생성 및 저장
 * @param int $userId 사용자 ID
 * @return string 생성된 CSRF 토큰
 */
function generateCsrfToken(int $userId): string {
    $token = bin2hex(random_bytes(32)); // 고유한 CSRF 토큰 생성
    $expiresAt = (new DateTime('+1 hour'))->format('Y-m-d H:i:s'); // 1시간 유효

    // 데이터베이스에 저장
    $db = getDbConnection();
    $stmt = $db->prepare("INSERT INTO csrf_tokens (token, user_id, expires_at) VALUES (?, ?, ?)");
    $stmt->execute([$token, $userId, $expiresAt]);

    return $token;
}

/**
 * CSRF 토큰 검증
 * @param string $token CSRF 토큰
 * @param int $userId 사용자 ID
 * @return bool 검증 결과
 */
function validateCsrfToken(string $token, int $userId): bool {
    $db = getDbConnection();

    // 토큰 확인 및 만료 체크
    $stmt = $db->prepare("SELECT * FROM csrf_tokens WHERE token = ? AND user_id = ? AND expires_at > NOW()");
    $stmt->execute([$token, $userId]);
    $result = $stmt->fetch();

    if ($result) {
        // 검증된 토큰 삭제(1회성 사용)
        $stmt = $db->prepare("DELETE FROM csrf_tokens WHERE id = ?");
        $stmt->execute([$result['id']]);
        return true;
    }

    return false;
}

 

3.2 폼 생성 및 CSRF 토큰 전송

form.php

<?php
require 'csrf.php';
session_start();

$userId = $_SESSION['user_id'] ?? 1; // 테스트용 사용자 ID
$csrfToken = generateCsrfToken($userId); // CSRF 토큰 생성
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>CSRF 방지 폼</title>
</head>
<body>
    <h1>CSRF 방지 폼</h1>
    <form action="process.php" method="POST">
        <label for="data">데이터 입력:</label>
        <input type="text" id="data" name="data" required>
        <!-- CSRF 토큰 숨김 필드 -->
        <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
        <button type="submit">전송</button>
    </form>
</body>
</html>

 

3.3 폼 데이터 처리 및 CSRF 검증

process.php

<?php
require 'csrf.php';
session_start();

$userId = $_SESSION['user_id'] ?? 1; // 테스트용 사용자 ID
$data = $_POST['data'] ?? '';
$csrfToken = $_POST['csrf_token'] ?? '';

// CSRF 토큰 검증
if (!validateCsrfToken($csrfToken, $userId)) {
    die("CSRF 검증 실패: 요청이 허용되지 않았습니다.");
}

// 데이터 처리
echo "데이터 처리 성공: " . htmlspecialchars($data);

 

3.4 데이터베이스 연결 설정

db.php

<?php
function getDbConnection() {
    $host = '127.0.0.1';
    $dbname = 'your_database';
    $username = 'your_username';
    $password = 'your_password';

    try {
        $db = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        return $db;
    } catch (PDOException $e) {
        die("데이터베이스 연결 실패: " . $e->getMessage());
    }
}

 

4. 주요 보안 고려사항

(1) 토큰 유효 기간: 데이터베이스에 만료 시간을 설정하여 일정 시간 이후 사용 불가 처리.

(2) HTTPS 사용: 토큰 전송 및 검증 시 HTTPS를 사용하여 데이터를 암호화.

(3) 1회성 토큰: 검증된 토큰은 즉시 삭제하여 재사용 방지.

(4) 사용자 고유 식별: 각 사용자마다 별도 토큰 관리.

(5) 세션 만료: 사용자가 로그아웃하거나 세션이 만료되면 토큰도 자동 무효화.

 

5. 결과 예제

(1) 입력 화면

폼에 CSRF 토큰이 포함되어 전송됨.

<form action="process.php" method="POST">
    <input type="text" name="data" required>
    <input type="hidden" name="csrf_token" value="generated_csrf_token">
    <button type="submit">전송</button>
</form>

 

(2) 검증 실패

유효하지 않은 토큰일 경우 요청이 거부됨.

CSRF 검증 실패: 요청이 허용되지 않았습니다.

 

(3) 성공 처리

검증 성공 시 요청 데이터 처리.

데이터 처리 성공: 사용자 입력 값

 

6. 결론

- 토큰 관리의 체계화

- 유효 기간 설정 및 재사용 방지

- 확장성과 추적 가능성 확보

 

With ChatGPT

'PHP > 포스팅' 카테고리의 다른 글

[PHP] 중요 데이터 금지  (0) 2024.12.27
[PHP] URL 길이 제한  (0) 2024.12.27
[PHP] CSRF 공격 방지  (0) 2024.12.26
[PHP] XSS 방지  (0) 2024.12.26
[PHP] GET 방식 폼 양식의 데이터 처리  (0) 2024.12.26