ภาษา PHP เบื้องต้น (4) การป้องกัน SQL Injection

Web Development 04 กันยายน พ.ศ. 2566 548
Home / Articles / 60

สวัสดีครับในบทความนี้จะเป็นการแนะนำการป้องกันเว็บไซต์ของเราจากการถูกโจมตีด้วย MySQL Injection ซึ่ง MySQL Injection คือการที่ผู้ไม่ประสงค์ดีสามารถแทรกโค้ดเข้าไปใน คำสั่ง MySQL ของเราได้ ซึ่งมีตัวอย่างดังนี้

สมมุตว่าผมต้องการทำระบบ ล๊อคอิน ผมก็จะเขียน PHP ประมาณนี้

//login.php
$username = $_POST['username'];
$password = $_POST['password'];
$check_login = mysqli_query($db, "SELECT * FROM admin WHERE username = '".$username."' AND password = '".$password."'");
if(mysqli_num_rows($check_login) == 1) {
  echo "Login Successfully.";
} else {
  echo "Login Failed."
}

เมื่อกรอกข้อมูลเข้าไปจะได้ query ที่หน้าตาแบบนี้

SELECT * FROM admin WHERE username = 'admin' AND password = '123';

หาก username และ password ตรงกับ ข้อมูลในฐานข้อมูล ก็จะถือว่่าข้อมูลการเข้าสู่ระบบนั้นถูกต้อง แต่หากใส่ข้อมูลในส่วนของ password ว่า 'OR 1=1

สิ่งที่จะเกิดขึ้นคือ แม้ข้อมูลจะไม่ตรงกับในฐานข้อมูล แต่ผู้ไม่หวังดีจะสามารถเข้าสู่ระบบได้อย่างง่ายดาย เนื่องจาก

เครื่องหมาย ' จะไปปิดเครื่องหมาย ' ที่อยู่ก่อนหน้า ทำให้ข้อความหลังจาก ' จะไม่ได้อยู่ใน String อีกต่อไป จะถือว่าเป็นคำสั่งของ MySQL พอมาเจอ OR 1=1 มันคือตรรกะที่เป็นจริงเสมอ ดังนั้น หากแปลคำสั่ง SQL มาเป็นข้อความตรรกะ จะได้ว่า

SELECT * FROM admin WHERE username = 'admin' AND password = '' OR 1=1;

เลือก ทั้งหมด จาก admin เมื่อ username = admin และ password = '' หรือ 1=1

จะพบว่าตรรกะนี้จะเป็นจริงเสมอ ทำให้สามารถเรียกแถวดังกล่าวได้ เพียงแค่ใช้ username ซึ่งวิธีการทำ SQL Injection นั้นมีมากมาย หลายวิธี ไม่ไ้ด้มีแค่ตรรกะ OR 1=1 เพียงอย่างเดียว และสามารถโจมตีคำสั่ง SELECT ในแบบอื่นได้ ผู้ไม่หวังดีสามารถใช้ช่องโหว่นี้ในการแทรกคำสั่ง SQL อื่นๆ เข้าไป เพื่อแสดงข้อมูลในตารางอื่นๆ ออกมาได้

วิธีป้องกัน SQL Injection

วิธีแรก คือการใช้ฟังก์ชั่น mysqli_real_escape_string() ฟังก์ชั่นนี้ทำงานโดยการตัดคำสั่งของ MySQL ออกจาก Strings โดยในกรณีนี้จะเอามาครอบ $_POST ไว้

//login.php
$username = mysqli_real_escape_string($db, $_POST['username']);
$password = mysqli_real_escape_string($db, $_POST['password']);
$check_login = mysqli_query($db, "SELECT * FROM admin WHERE username = '".$username."' AND password = '".$password."'");
if(mysqli_num_rows($check_login) == 1) {
  echo "Login Successfully.";
} else {
  echo "Login Failed."
}

วิธีที่สอง ซึ่งได้รับความนิยมมากที่สุด คือการ bind_params คือการที่เราจะใช้ฟังก์ชั่น mysqli_prepare() ก่อน และจะ bind params ซึ่งกรณีตัวอย่างคือ $username $password เข้าไปในคำสั่ง SQL ทีหลัง ตัวอย่างดังนี้

//login.php
$username = $_POST['username'];
$password = $_POST['password'];
$check_login = mysqli_prepare($db, "SELECT * FROM admin WHERE username = ? AND password = ?");
mysqli_stmt_bind_param($check_login, 'ss', $username, $password);
mysqli_stmt_execute($check_login);
mysqli_stmt_store_result($check_login);
if(mysqli_stmt_num_rows($check_login) == 1) {
  echo "Login Successfully.";
} else {
  echo "Login Failed."
}

อธิบายเพิ่มเติมในฟังก์ชั่น mysqli_stmt_bind_param() ในส่วน 'ss' คือการแทนที่ $username กับ $password ลงไปใน ? ตามลำดับ โดยที่ตัวอักษร s หมายถึงชนิดของข้อมูลที่เป็น String

อ้างอิง:

www.w3schools.com/php/func_mysqli_real_escape_string.asp

www.tutorialspoint.com/php/php_function_mysqli_stmt_bind_param.htm

Profile Picture.
  • Name (Pen name): Sunny Jirakit (Sunny420x)
  • Study: Bachelor Degree of Computer Science from Chiang Mai Rajabhat University
  • Personality: Architect (INTJ-T)
  • Experience: JavaScript,  Angular.js, React.js, Next.js  Express.js, Unity C#, Socket.io