본문으로 건너뛰기

เนื้อหาภาษาไทย / 태국어 콘텐츠

시스템 가이드 검수 완료된 콘텐츠

การป้องกัน SQL Injection: การใช้งาน Prepared Statements อย่างถูกต้อง

บทความนี้กล่าวถึง SQL Injection ซึ่งเป็นช่องโหว่ที่ผู้ไม่หวังดีสามารถแทรกคำสั่ง SQL อันตรายเข้าไปใน query ได้ โดยเน้นย้ำถึงการป้องกันด้วย Prepared Statements ซึ...

การป้องกัน SQL Injection: การใช้งาน Prepared Statements อย่างถูกต้อง

ในบทเรียนก่อนหน้านี้ เราได้เรียนรู้เกี่ยวกับการทำความสะอาดข้อมูล (Data Sanitization) เพื่อป้องกันข้อมูลที่ไม่พึงประสงค์เข้าสู่ระบบของเรา แต่การทำความสะอาดข้อมูลเพียงอย่างเดียวอาจไม่เพียงพอที่จะป้องกันการโจมตีที่เรียกว่า SQL Injection ได้อย่างสมบูรณ์แบบ ในบทนี้ เราจะมาเจาะลึกถึงวิธีการป้องกัน SQL Injection อย่างมีประสิทธิภาพด้วยการใช้งาน Prepared Statements อย่างถูกต้อง

SQL Injection คืออะไร?

SQL Injection คือช่องโหว่ด้านความปลอดภัยที่เกิดขึ้นเมื่อผู้ไม่หวังดีสามารถแทรกคำสั่ง SQL ที่เป็นอันตรายเข้าไปใน query ที่แอปพลิเคชันของคุณส่งไปยังฐานข้อมูล ซึ่งอาจนำไปสู่การเข้าถึงข้อมูลที่ไม่ได้รับอนุญาต การแก้ไข หรือแม้กระทั่งการลบข้อมูลในฐานข้อมูลของคุณ

ตัวอย่างเช่น หากคุณมี form ที่ให้ผู้ใช้กรอกชื่อผู้ใช้ และนำข้อมูลนั้นไปใช้ใน query ดังนี้:

$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = $conn->query($sql);

หากผู้ใช้กรอกชื่อผู้ใช้เป็น ' OR '1'='1, query จะกลายเป็น:

SELECT * FROM users WHERE username = '' OR '1'='1'

เนื่องจาก '1'='1' เป็นจริงเสมอ, query นี้จะดึงข้อมูลผู้ใช้ทั้งหมดในฐานข้อมูล ซึ่งเป็นการละเมิดความปลอดภัยอย่างร้ายแรง

Prepared Statements: เกราะป้องกัน SQL Injection

Prepared Statements เป็นวิธีที่ปลอดภัยกว่าในการสร้าง query SQL เนื่องจากแยกส่วนของ query ที่เป็นโครงสร้าง (template) ออกจากข้อมูลที่ผู้ใช้ป้อน (data) ทำให้ฐานข้อมูลสามารถประมวลผลโครงสร้างของ query ล่วงหน้า และ treat ข้อมูลที่ผู้ใช้ป้อนเป็นเพียงข้อมูล ไม่ใช่ส่วนหนึ่งของคำสั่ง SQL

Prepared Statements ทำงานโดย:

  1. เตรียม (Prepare) query: ส่งโครงสร้างของ query ไปยังฐานข้อมูล โดยมี placeholder สำหรับข้อมูลที่ผู้ใช้ป้อน
  2. Bind parameters: ผูกข้อมูลที่ผู้ใช้ป้อนกับ placeholder
  3. Execute: สั่งให้ฐานข้อมูลรัน query

ด้วยวิธีนี้ ฐานข้อมูลจะรู้ว่าส่วนใดของ query คือโครงสร้าง และส่วนใดคือข้อมูล ทำให้ไม่สามารถตีความข้อมูลที่ผู้ใช้ป้อนเป็นคำสั่ง SQL ได้

การใช้งาน Prepared Statements อย่างถูกต้อง

ในการใช้งาน Prepared Statements อย่างถูกต้อง คุณต้องหลีกเลี่ยงการใช้ string concatenation เพื่อสร้าง query และใช้ฟังก์ชัน `bindParam()` หรือ `bindValue()` เพื่อผูกข้อมูลกับ placeholder

การหลีกเลี่ยงการใช้ String Concatenation

การใช้ string concatenation เพื่อสร้าง query จะทำให้คุณเสี่ยงต่อ SQL Injection แม้ว่าคุณจะพยายามทำความสะอาดข้อมูลแล้วก็ตาม ตัวอย่างที่ไม่ควรทำ:

$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '" . $username . "' AND password = '" . $password . "'"; // ไม่ปลอดภัย!
$result = $conn->query($sql);

ตัวอย่างนี้ยังคงเปิดช่องโหว่ให้ผู้ไม่หวังดีสามารถแทรกคำสั่ง SQL ที่เป็นอันตรายเข้าไปใน query ได้

การใช้ `bindParam()` หรือ `bindValue()`

`bindParam()` และ `bindValue()` เป็นฟังก์ชันที่ใช้ในการผูกข้อมูลกับ placeholder ใน Prepared Statements ทั้งสองฟังก์ชันมีความคล้ายคลึงกัน แต่มีความแตกต่างที่สำคัญ:

  • `bindParam()`: ผูกตัวแปรกับ placeholder โดยการอ้างอิง (reference) หากค่าของตัวแปรเปลี่ยนแปลงหลังจากผูกแล้ว ค่าใน query ก็จะเปลี่ยนแปลงตาม
  • `bindValue()`: ผูกค่าของตัวแปรกับ placeholder โดยการคัดลอก (copy) หากค่าของตัวแปรเปลี่ยนแปลงหลังจากผูกแล้ว ค่าใน query จะไม่เปลี่ยนแปลง

สำหรับกรณีส่วนใหญ่ `bindValue()` เป็นตัวเลือกที่ปลอดภัยกว่า เนื่องจากป้องกันการเปลี่ยนแปลงค่าโดยไม่ได้ตั้งใจ

ตัวอย่างการใช้งาน `bindValue()`:

$username = $_POST['username'];
$password = $_POST['password'];

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->bindValue(':username', $username, PDO::PARAM_STR);
$stmt->bindValue(':password', $password, PDO::PARAM_STR); // ควร hash password ก่อน!
$stmt->execute();

$user = $stmt->fetch(PDO::FETCH_ASSOC);

ในตัวอย่างนี้ เราได้ใช้ `bindValue()` เพื่อผูกค่าของ `$username` และ `$password` กับ placeholder `:username` และ `:password` โดยระบุประเภทของข้อมูลเป็น `PDO::PARAM_STR` (string) การทำเช่นนี้จะทำให้ฐานข้อมูล treat ค่าเหล่านี้เป็นข้อมูล ไม่ใช่ส่วนหนึ่งของคำสั่ง SQL

ตัวอย่างการใช้งาน `bindParam()`:

$username = $_POST['username'];
$password = $_POST['password'];

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
$stmt->bindParam(':password', $password, PDO::PARAM_STR); // ควร hash password ก่อน!
$stmt->execute();

$user = $stmt->fetch(PDO::FETCH_ASSOC);

การใช้งาน `bindParam()` ในลักษณะนี้ให้ผลลัพธ์เหมือนกับ `bindValue()` แต่หากคุณมีการเปลี่ยนแปลงค่าของ `$username` หรือ `$password` หลังจากเรียก `bindParam()` ค่าใน query ก็จะเปลี่ยนแปลงตาม

การตรวจสอบ SQL Query

แม้ว่าคุณจะใช้ Prepared Statements แล้ว ก็ควรตรวจสอบ SQL query ที่ถูกสร้างขึ้นเสมอ เพื่อให้แน่ใจว่าไม่มีอะไรผิดพลาดเกิดขึ้น คุณสามารถใช้ฟังก์ชัน `debugDumpParams()` เพื่อดู query และ parameters ที่ถูกส่งไปยังฐานข้อมูล:

$username = $_POST['username'];
$password = $_POST['password'];

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->bindValue(':username', $username, PDO::PARAM_STR);
$stmt->bindValue(':password', $password, PDO::PARAM_STR); // ควร hash password ก่อน!
$stmt->execute();

$stmt->debugDumpParams(); // แสดง query และ parameters

$user = $stmt->fetch(PDO::FETCH_ASSOC);

ฟังก์ชันนี้จะแสดงข้อมูลที่เป็นประโยชน์สำหรับการ debugging เช่น:

SQL: [92] SELECT * FROM users WHERE username = :username AND password = :password
Params: 2
Key: Name: [8] :username
paramno=0
name=[8] ":username"
is_param=1
param_type=2
Key: Name: [8] :password
paramno=1
name=[8] ":password"
is_param=1
param_type=2

จากข้อมูลนี้ คุณสามารถตรวจสอบได้ว่า query ถูกสร้างขึ้นอย่างถูกต้อง และ parameters ถูกผูกกับ placeholder อย่างถูกต้อง

สรุป

การป้องกัน SQL Injection เป็นสิ่งสำคัญอย่างยิ่งในการพัฒนาเว็บแอปพลิเคชัน การใช้งาน Prepared Statements อย่างถูกต้อง โดยหลีกเลี่ยงการใช้ string concatenation และใช้ `bindParam()` หรือ `bindValue()` จะช่วยปกป้องฐานข้อมูลของคุณจากการโจมตีที่อาจเกิดขึ้น นอกจากนี้ การตรวจสอบ SQL query ที่ถูกสร้างขึ้นยังเป็นขั้นตอนสำคัญในการตรวจสอบความถูกต้องและความปลอดภัยของโค้ดของคุณ

ในบทเรียนถัดไป เราจะมาเรียนรู้เกี่ยวกับการป้องกัน CSRF (Cross-Site Request Forgery) ซึ่งเป็นอีกหนึ่งช่องโหว่ด้านความปลอดภัยที่พบบ่อยในเว็บแอปพลิเคชัน

Office.in.th Custom Service

이 구조를 바탕으로 시스템을 개발하거나 맞춤 변경하고 싶으신가요?

Office.in.th는 엔터프라이즈 웹 애플리케이션, API 연동, 공공/민간 업무 관리 시스템을 설계·개발하며 장기 지원을 제공합니다.

소프트웨어 개발 서비스 보기