การปกป้องเว็บแอปพลิเคชันจากภัยคุกคามทางไซเบอร์เป็นเรื่องที่ไม่ควรมองข้าม และหนึ่งในช่องโหว่พื้นฐานที่พบได้บ่อยที่สุดคือ CSRF (Cross-Site Request Forgery) หรือการปลอมแปลงคำร้องขอจากฝั่งผู้ใช้
บทความนี้จะพาคุณไปเข้าใจหลักการของ CSRF และเขียนโค้ด PHP ในการสร้างและตรวจสอบ CSRF Token ด้วยตัวเองอย่างง่าย แต่ได้ผลจริงครับ
CSRF คืออะไร? ทำไมต้องป้องกัน?
CSRF คือการที่ผู้ไม่หวังดี (Hacker) หลอกให้ผู้ใช้ที่กำลังเข้าสู่ระบบ (Log in) อยู่ในเว็บของเรา กดลิงก์หรือส่งฟอร์มจากเว็บอื่น โดยที่ผู้ใช้ไม่รู้ตัว ทำให้ระบบคิดว่าเป็นคำสั่งที่มาจากผู้ใช้จริง เช่น การแอบเปลี่ยนรหัสผ่าน หรือการโอนเงิน
แก้ปัญหาอย่างไร? วิธีป้องกันที่ดีที่สุดคือการใช้ CSRF Token ซึ่งเป็นรหัสสุ่มสั้นๆ ที่สร้างขึ้นฝั่งเซิร์ฟเวอร์ และแนบไปกับฟอร์ม เมื่อผู้ใช้ส่งฟอร์มกลับมา เซิร์ฟเวอร์จะเช็กว่า Token ตรงกันหรือไม่ ถ้าตรงกันถึงจะยอมให้ทำรายการ
ขั้นตอนการสร้างและตรวจสอบ CSRF Token ใน PHP
การทำงานแบ่งออกเป็น 3 ส่วนหลัก ๆ ดังนี้
1. การสร้าง Token (Generate Token)
เราจะสร้าง Token โดยใช้ฟังก์ชัน random_bytes() เพื่อความปลอดภัยสูง และเก็บไว้ใน $_SESSION
<?php
// ต้องเริ่ม Session ทุกครั้งก่อนใช้งาน
session_start();
// ฟังก์ชันสร้าง CSRF Token
function generateCsrfToken() {
if (empty($_SESSION['csrf_token'])) {
// สร้าง รหัสสุ่มความยาว 32 bytes แล้วแปลงเป็น hex (64 ตัวอักษร)
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
$token = generateCsrfToken();
?>
2. การแนบ Token ไปกับ HTML Form
ฝัง Token ไว้ในฟอร์มโดยใช้ <input type="hidden"> เพื่อไม่ให้รบกวนหน้าตาของเว็บ
<form action="process.php" method="POST">
<!-- แนบ CSRF Token ไว้ใน field แบบ hidden -->
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($token); ?>">
<label for="username">ชื่อผู้ใช้งาน:</label>
<input type="text" id="username" name="username" required>
<button type="submit">บันทึกข้อมูล</button>
</form>
3. การตรวจสอบ Token ฝั่ง Server (process.php)
เมื่อมีการกดส่งฟอร์ม เราจะนำ Token ที่ส่งมาผ่าน $_POST ไปเปรียบเทียบกับ Token ที่อยู่ใน $_SESSION โดยใช้ฟังก์ชัน hash_equals() เพื่อป้องกันการโจมตีแบบ Timing Attack
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$userToken = $_POST['csrf_token'] ?? '';
$sessionToken = $_SESSION['csrf_token'] ?? '';
// ตรวจสอบความถูกต้องของ Token
if (empty($userToken) || !hash_equals($sessionToken, $userToken)) {
// ถ้า Token ไม่ตรงกัน ให้ปฏิเสธการทำรายการทันที
http_response_code(403);
die("Error: การตรวจสอบ CSRF Token ล้มเหลว (Invalid CSRF Token)");
}
// --- ผ่านการตรวจสอบ! สามารถประมวลผลข้อมูลฟอร์มต่อได้ปลอดภัย ---
$username = $_POST['username'] ?? '';
// (ตัวอย่าง) ทำการบันทึกข้อมูลลง Database
echo "บันทึกข้อมูลสำเร็จ! ยินดีต้อนรับ " . htmlspecialchars($username);
// คำแนะนำ: ควรลบหรือหมุนเวียน Token ใหม่หลังจากใช้งานเสร็จแล้ว
unset($_SESSION['csrf_token']);
} else {
// ปฏิเสธหากไม่ได้ส่งมาด้วย POST
header("Location: index.php");
exit;
}
?>
3 ข้อควรจำเพื่อความปลอดภัยสูงสุด
- ใช้
hash_equals()เสมอ: หลีกเลี่ยงการใช้==หรือ===ในการเปรียบเทียบ Token เพราะอาจเปิดช่องโหว่ด้าน Timing Attack ได้ - ใช้
random_bytes(): อย่าใช้ฟังก์ชันสุ่มแบบเก่าอย่างrand()หรือuniqid()เพราะเดารูปแบบได้ง่าย - regenerate token เมื่อสำคัญ: สำหรับฟอร์มที่สำคัญมากๆ เช่น เปลี่ยนรหัสผ่าน หรือโอนเงิน ควรทำการลบ Token เก่าทิ้ง (
unset) แล้วสร้างใหม่ทุกครั้งหลังใช้งานเสร็จ
อ่านเพิ่มเติม