ป้ายกำกับ: Infer

PHP: การป้องกันการโจมตีแบบ Timing Attack ด้วย hash_equals()PHP: การป้องกันการโจมตีแบบ Timing Attack ด้วย hash_equals()

ในโลกของการพัฒนาเว็บแอปพลิเคชันที่เกี่ยวข้องกับการตรวจสอบความถูกต้องของข้อมูลลับ (Secrets) เช่น รหัสผ่าน, โทเคน (Tokens), หรือค่า HMAC การเปรียบเทียบสตริง (String Comparison) เป็นขั้นตอนพื้นฐาน แต่หากเราใช้ฟังก์ชันการเปรียบเทียบแบบมาตรฐานทั่วไปอย่าง == หรือ strcmp() อาจนำไปสู่ช่องโหว่ที่เรียกว่า Timing Attack ได้

Timing Attack คืออะไร? มันคือรูปแบบของการโจมตีทางด้านความปลอดภัยที่ผู้โจมตีไม่ได้พยายามเดาค่าลับโดยตรง แต่เป็นการวัดเวลา (Time Measurement) ที่ระบบใช้ในการประมวลผลการเปรียบเทียบข้อมูล เมื่อฟังก์ชันเปรียบเทียบสตริงแบบปกติพบว่าอักขระตัวแรกไม่ตรงกัน มันจะหยุดทำงานและคืนค่าทันที ในขณะที่ถ้ามันต้องตรวจสอบไปจนถึงอักขระตัวสุดท้ายแล้วจึงพบความแตกต่าง เวลาที่ใช้ในการประมวลผลก็จะนานกว่าอย่างมีนัยสำคัญ ผู้โจมตีสามารถวัดความแตกต่างของเวลานี้เพื่ออนุมาน (Infer) ว่าข้อมูลลับนั้นถูกต้องไปเรื่อยๆ ทีละไบต์ จนกระทั่งได้ค่าที่แท้จริง

หลักการทำงานของ hash_equals(): เพื่อป้องกันปัญหานี้ เราจำเป็นต้องใช้ฟังก์ชันเปรียบเทียบแบบ Constant-Time Comparison (การเปรียบเทียบเวลาคงที่) ซึ่งหมายความว่า ไม่ว่าสตริงสองตัวจะแตกต่างกันแค่ไบต์แรก หรือแตกต่างกันแค่ไบต์สุดท้าย ฟังก์ชันจะต้องถูกออกแบบให้ทำงานและใช้เวลาในการประมวลผลเท่ากันเสมอ โดยไม่ขึ้นอยู่กับตำแหน่งที่เกิดความผิดพลาด (Mismatch)

ใน PHP, hash_equals() คือฟังก์ชันมาตรฐานที่รับประกันการเปรียบเทียบแบบ Constant-Time ทำให้มั่นใจได้ว่าแม้ผู้โจมตีจะวัดเวลาในการตอบสนองของเซิร์ฟเวอร์ พวกเขาจะไม่สามารถใช้ความแตกต่างของเวลานั้นเพื่อหาข้อมูลลับใดๆ ได้


ตัวอย่างการใช้งานและรูปแบบโค้ด (Code Examples)

สมมติว่าเรากำลังตรวจสอบความถูกต้องของค่า HMAC ที่ส่งมาจากไคลเอนต์ ซึ่งเป็นกระบวนการที่ต้องใช้ความปลอดภัยสูงสุด การเปรียบเทียบด้วย == ถือว่าอันตรายอย่างยิ่ง


❌ วิธีที่ไม่ปลอดภัย (Vulnerable Code: Using ==)

โค้ดนี้จะหยุดทำงานทันทีที่พบความแตกต่าง ทำให้ผู้โจมตีสามารถวัดเวลาเพื่อเดาค่า HMAC ได้

<?php
// ค่า HMAC ที่ถูกเก็บไว้ในฐานข้อมูล (The stored secret)
$stored_hmac = "a1b2c3d4e5f67890abcdef";

// ค่าที่ผู้ใช้ส่งมาเพื่อตรวจสอบ (User provided input)
$user_input_hmac = "a1b2c3d4e5f6789Xefgh"; // ผิดแค่ไบต์สุดท้าย

// ⚠️ อันตราย: การเปรียบเทียบแบบมาตรฐานจะหยุดทำงานทันทีที่พบความต่าง (Mismatch at 'X')
if ($stored_hmac === $user_input_hmac) {
    echo "Success! HMAC is valid.";
} else {
    // เวลาที่ใช้ในการประมวลผลจะสั้นกว่าการเปรียบเทียบที่ถูกต้องมาก
    echo "Error: Invalid HMAC. (Timing vulnerability exists)"; 
}
?>

✅ วิธีที่ปลอดภัย (Secure Code: Using hash_equals())

การใช้ hash_equals() จะบังคับให้ระบบเปรียบเทียบทุกไบต์อย่างเท่าเทียมกัน ไม่ว่าความผิดพลาดจะเกิดขึ้นเมื่อใดก็ตาม

<?php
// ค่า HMAC ที่ถูกเก็บไว้ในฐานข้อมูล (The stored secret)
$stored_hmac = "a1b2c3d4e5f67890abcdef";

// ค่าที่ผู้ใช้ส่งมาเพื่อตรวจสอบ (User provided input)
$user_input_hmac = "a1b2c3d4e5f6789Xefgh"; // ผิดแค่ไบต์สุดท้าย

// ✅ ปลอดภัย: hash_equals() รับประกันการเปรียบเทียบแบบ Constant-Time 
if (hash_equals($stored_hmac, $user_input_hmac)) {
    echo "Success! HMAC is valid.";
} else {
    // ระบบจะใช้เวลาในการประมวลผลที่สม่ำเสมอ ไม่ว่าข้อมูลจะผิดแค่ไหนก็ตาม
    echo "Error: Invalid HMAC. (Secure comparison performed)"; 
}
?>

ข้อควรระวัง Security และ Best Practices

  • คำนึงถึงความปลอดภัย (Security Focus): hash_equals() ควรใช้เสมอเมื่อเปรียบเทียบค่าแฮชหรือโทเคนที่มาจากแหล่งภายนอก (User Input) กับค่าที่เก็บไว้ในระบบ การป้องกันนี้เป็นสิ่งจำเป็นสำหรับฟังก์ชันการตรวจสอบสิทธิ์ทั้งหมด
  • Performance: แม้ว่า hash_equals() จะมีความปลอดภัยสูง แต่โดยธรรมชาติแล้วมันจะทำงานช้ากว่าการเปรียบเทียบแบบปกติเล็กน้อย (เพราะต้องบังคับให้รันจนจบ) อย่างไรก็ตาม ความแตกต่างของเวลาที่เพิ่มขึ้นนี้ถือเป็น “ต้นทุนด้านความปลอดภัย” ที่ยอมรับได้ และไม่ควรถูกมองข้าม
  • Best Practice: การใช้ฟังก์ชันแฮชที่เหมาะสม: ก่อนที่จะถึงขั้นตอนการเปรียบเทียบด้วย hash_equals() สิ่งสำคัญที่สุดคือการใช้ฟังก์ชันแฮชที่มี Salt และ Work Factor สูง เช่น Argon2 หรือ Bcrypt สำหรับรหัสผ่าน (Password Hashing) อย่าลืมว่า hash_equals() ใช้สำหรับ *การเปรียบเทียบ* ค่าที่ถูกแฮชแล้ว ไม่ใช่สำหรับการสร้างค่าแฮช
  • Error Handling: ควรตรวจสอบเสมอว่าตัวแปรที่จะนำมาเปรียบเทียบนั้นเป็นสตริงและมีความยาวเท่ากันหรือไม่ หากความยาวต่างกัน การเรียกใช้ hash_equals() อาจให้ผลลัพธ์ที่ไม่คาดคิด หรืออาจต้องมีการตัด/เติม Padding ให้มีขนาดเท่ากันก่อน (แต่โดยทั่วไปแล้วการรับค่าที่มาจากแหล่งเดียวกันควรจะมีความยาวเท่ากันอยู่แล้ว)

สรุปและการนำไปประยุกต์ใช้งาน

การใช้ hash_equals() ไม่ใช่แค่ทางเลือก แต่เป็นมาตรฐานด้านความปลอดภัยที่ต้องปฏิบัติตามเมื่อมีการเปรียบเทียบค่าแฮชหรือโทเคนใน PHP การทำเช่นนี้เป็นการป้องกันช่องโหว่ระดับสูงที่ผู้โจมตีสามารถใช้เวลาในการวัดผลเพื่อเจาะระบบได้

กรณีการนำไปใช้งานจริง (Production Use Cases):

  • การตรวจสอบค่า HMAC ที่ส่งมากับ API Request
  • การเปรียบเทียบ Session Tokens หรือ JWT Signatures
  • การยืนยันรหัสผ่านที่ผู้ใช้กรอกเข้ามาหลังจากการแฮชด้วยฟังก์ชันเฉพาะ (เช่น การใช้ password_verify() ซึ่งภายในก็มีการจัดการความปลอดภัยของเวลาให้แล้ว แต่หากต้องทำ Manual Comparison ก็ควรใช้ hash_equals())

การใส่ใจในรายละเอียดเล็กๆ น้อยๆ อย่าง Constant-Time Comparison นี้ จะช่วยยกระดับความแข็งแกร่งด้านความปลอดภัยของแอปพลิเคชันของคุณได้อย่างมหาศาล ทำให้ระบบทนทานต่อการโจมตีที่ซับซ้อนและล้ำหน้ายิ่งขึ้น


อ่านเพิ่มเติม