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 |