การใช้ Prepared Statements กับ LIKE Operator อย่างปลอดภัย
ในบทก่อนหน้า เราได้เรียนรู้เกี่ยวกับ Session Hijacking และวิธีการป้องกัน ในบทนี้ เราจะมาเจาะลึกการใช้งาน Prepared Statements กับ LIKE operator อย่างปลอดภัย ซึ่งเป็นสิ่งสำคัญอย่างยิ่งในการป้องกัน SQL Injection เมื่อมีการค้นหาข้อมูลด้วยเงื่อนไขที่ผู้ใช้ป้อนเข้ามา
ทำไมต้องระวังเมื่อใช้ LIKE กับ Prepared Statements?
LIKE operator ใช้สำหรับค้นหาข้อมูลที่ตรงกับรูปแบบ (pattern) ที่กำหนด โดยมักใช้ร่วมกับ wildcard characters เช่น % (แทนอักขระใดๆ ก็ได้ 0 ตัวขึ้นไป) และ _ (แทนอักขระใดๆ ก็ได้ 1 ตัว) หากเรานำค่าจากผู้ใช้มาใส่ใน pattern ของ LIKE โดยตรง โดยไม่มีการตรวจสอบหรือป้องกัน อาจทำให้เกิดช่องโหว่ SQL Injection ได้
ยกตัวอย่างเช่น หากผู้ใช้ป้อนค่า "test%" แล้วเรานำค่านี้ไปใช้ใน query โดยตรง อาจทำให้ผู้ไม่ประสงค์ดีสามารถใส่ SQL code ที่เป็นอันตรายลงไปใน pattern ได้
การ Escape Wildcard Characters
ขั้นตอนแรกในการใช้งาน LIKE กับ Prepared Statements อย่างปลอดภัย คือการ escape wildcard characters ที่อาจมีอยู่ใน input ของผู้ใช้ เพื่อให้ % และ _ ถูกตีความเป็น literal characters แทนที่จะเป็น wildcard characters
เราสามารถใช้ฟังก์ชัน str_replace() ใน PHP เพื่อ escape wildcard characters ได้ดังนี้:
<?php
function escapeLikeWildcards(string $input): string {
$escaped = str_replace(['%', '_'], ['\%', '\_'], $input);
return $escaped;
}
$userInput = $_GET['search']; // สมมติว่ารับค่ามาจาก GET request
$escapedInput = escapeLikeWildcards($userInput);
echo "Escaped input: " . $escapedInput;
?>
ในตัวอย่างนี้ ฟังก์ชัน escapeLikeWildcards() จะทำการแทนที่ % ด้วย \% และ _ ด้วย \_ ใน input string ทำให้ wildcard characters ถูก escape และตีความเป็น literal characters
เหตุผลเชิงออกแบบ: การสร้างฟังก์ชัน escapeLikeWildcards() ช่วยให้โค้ดของเรามีความ reusable และอ่านง่ายขึ้น นอกจากนี้ยังช่วยให้เราสามารถปรับเปลี่ยนวิธีการ escape wildcard characters ได้ง่าย หากจำเป็น
การใช้ bindParam() กับ LIKE
หลังจาก escape wildcard characters แล้ว เราสามารถใช้ bindParam() เพื่อ bind ค่าที่ escape แล้วเข้ากับ Prepared Statement ได้อย่างปลอดภัย
<?php
// สมมติว่ามีการเชื่อมต่อฐานข้อมูล PDO แล้ว ($pdo)
$userInput = $_GET['search'];
$escapedInput = escapeLikeWildcards($userInput);
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->bindParam(':search', '%' . $escapedInput . '%', PDO::PARAM_STR); // เพิ่ม % เพื่อให้ค้นหาแบบ contains
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// แสดงผลลัพธ์
foreach ($results as $row) {
echo $row['name'] . "<br>";
}
?>
ในตัวอย่างนี้ เราได้ทำการ:
- Escape wildcard characters ใน
$userInput - สร้าง Prepared Statement ที่ใช้
LIKE :search - ใช้
bindParam()เพื่อ bind ค่า'%' . $escapedInput . '%'เข้ากับ placeholder:search - เพิ่ม
%ทั้งด้านหน้าและด้านหลังของ$escapedInputเพื่อให้ค้นหาข้อมูลที่มี$escapedInputเป็นส่วนหนึ่งของชื่อ (contains) - Execute query และดึงข้อมูล
เหตุผลเชิงออกแบบ: การใช้ bindParam() ช่วยป้องกัน SQL Injection โดยการแยก query structure ออกจาก data ทำให้ database engine สามารถ compile query ได้ก่อนที่จะรับ data จากผู้ใช้ ทำให้ไม่สามารถแทรก SQL code ที่เป็นอันตรายเข้าไปใน query ได้
ข้อควรระวัง: การใส่ % ก่อนและหลัง $escapedInput ใน bindParam() เป็นสิ่งที่จำเป็นหากเราต้องการค้นหาข้อมูลที่ *มี* ค่าที่ผู้ใช้ป้อนเข้ามาเป็นส่วนหนึ่งของข้อมูล หากเราต้องการค้นหาข้อมูลที่ *เริ่มต้นด้วย* ค่าที่ผู้ใช้ป้อนเข้ามา เราสามารถใส่ % ไว้แค่ด้านหลังของ $escapedInput เท่านั้น
ตัวอย่างการใช้งาน LIKE แบบต่างๆ
ต่อไปนี้เป็นตัวอย่างการใช้งาน LIKE แบบต่างๆ:
- ค้นหาข้อมูลที่เริ่มต้นด้วยคำที่ผู้ใช้ป้อน:
<?php
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->bindParam(':search', $escapedInput . '%', PDO::PARAM_STR);
$stmt->execute();
?>
<?php
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->bindParam(':search', '%' . $escapedInput, PDO::PARAM_STR);
$stmt->execute();
?>
<?php
// ไม่แนะนำ! ควรใช้ WHERE name = :search แทน
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->bindParam(':search', $escapedInput, PDO::PARAM_STR);
$stmt->execute();
?>
แนวปฏิบัติที่ดี:
- ตรวจสอบ input ของผู้ใช้เสมอ: ก่อนที่จะนำ input ของผู้ใช้มาใช้ใน query ใดๆ ควรตรวจสอบ input นั้นก่อนเสมอ เพื่อให้แน่ใจว่าเป็นไปตามรูปแบบที่คาดหวัง
- ใช้ Prepared Statements เสมอ: หลีกเลี่ยงการสร้าง query โดยการต่อ string โดยตรง เพราะอาจทำให้เกิดช่องโหว่ SQL Injection ได้
- Escape wildcard characters: เมื่อใช้
LIKEoperator ให้ escape wildcard characters เสมอ - ใช้
bindParam(): ใช้bindParam()เพื่อ bind ค่าเข้ากับ Prepared Statement อย่างปลอดภัย - ใช้ `=` แทน LIKE สำหรับ exact match: หากต้องการค้นหาข้อมูลที่ตรงกับค่าที่กำหนดอย่างแม่นยำ ควรใช้ `=` operator แทน
LIKEoperator
สรุป
การใช้ Prepared Statements กับ LIKE operator อย่างปลอดภัย เป็นสิ่งสำคัญในการป้องกัน SQL Injection เมื่อมีการค้นหาข้อมูลด้วยเงื่อนไขที่ผู้ใช้ป้อนเข้ามา โดยเราต้องทำการ escape wildcard characters และใช้ bindParam() เพื่อ bind ค่าเข้ากับ Prepared Statement อย่างถูกต้อง นอกจากนี้ เราควรตรวจสอบ input ของผู้ใช้เสมอ และใช้ `=` operator แทน LIKE operator หากต้องการค้นหาข้อมูลที่ตรงกับค่าที่กำหนดอย่างแม่นยำ
ในบทถัดไป เราจะเรียนรู้เกี่ยวกับการใช้ PDO กับ UNION statement ซึ่งเป็นอีกหนึ่งเทคนิคที่สำคัญในการจัดการฐานข้อมูล