MySQL ความปลอดภัยและการจัดการสิทธิ์ของ Stored Procedure

Stored Procedure มีความสามารถในการจัดการกับข้อมูลในหลายตารางได้ในเวลาเดียวกัน
นั่นหมายความว่าถ้าใช้งานไม่ระวัง ก็อาจทำให้เกิดปัญหาด้านความปลอดภัย, การควบคุมสิทธิ์, หรือข้อมูลรั่วไหลได้

MySQL จึงมีเครื่องมือที่ช่วยให้เราสามารถควบคุมการเข้าถึงและการทำงานของ Procedure ได้อย่างเหมาะสม เช่น

  • SQL SECURITY
  • GRANT EXECUTE
  • การแยกระดับผู้ใช้
  • การป้องกัน SQL Injection

SQL SECURITY ควบคุมว่าคำสั่งจะรันด้วยสิทธิ์ของใคร

เมื่อสร้าง Stored Procedure เราสามารถกำหนดได้ว่า MySQL ควรใช้สิทธิ์ของใครในการรันคำสั่งภายในนั้น

SQL
CREATE DEFINER = 'admin'@'localhost'
PROCEDURE my_proc()
SQL SECURITY [DEFINER|INVOKER]
BEGIN
  ...
END;
  • DEFINER หมายความว่า คำสั่งภายใน Procedure จะรันด้วยสิทธิ์ของผู้สร้าง (เจ้าของ Procedure)
  • INVOKER หมายความว่า คำสั่งจะรันด้วยสิทธิ์ของผู้ที่เรียกใช้ Procedure นั้น ๆ
  • ค่า Default คือ DEFINER

ตัวอย่าง: ให้คนเรียกใช้ดูเฉพาะข้อมูลตัวเองเท่านั้น

SQL
CREATE DEFINER = 'admin'@'localhost'
PROCEDURE view_my_orders(IN user_id INT)
SQL SECURITY INVOKER
BEGIN
  SELECT * FROM orders WHERE customer_id = user_id;
END;
  • แม้ Procedure จะเข้าถึงตาราง orders ได้ แต่หากผู้ใช้คนนั้นไม่มีสิทธิ์เข้าถึง orders โดยตรง ก็จะถูกบล็อกโดยอัตโนมัติ

GRANT EXECUTE ควบคุมว่าใครเรียก Procedure ได้บ้าง

เราสามารถกำหนดสิทธิ์การเรียกใช้งาน Stored Procedure ด้วยคำสั่ง GRANT

GRANT EXECUTE ON PROCEDURE db_name.proc_name TO 'username'@'host';

ตัวอย่าง

SQL
GRANT EXECUTE ON PROCEDURE mydb.process_order TO 'staff'@'%';
  • หากไม่ได้ให้สิทธิ์ EXECUTE ผู้ใช้จะไม่สามารถเรียก CALL process_order() ได้เลย

การแยกระดับผู้ใช้งาน (User Roles)

ควรแยก User สำหรับดูข้อมูล (Read-only) และจัดการข้อมูล (Admin) ออกจากกัน หากใช้ Procedure ที่มีคำสั่งสำคัญ เช่น DELETE, UPDATE ควรกำหนดสิทธิ์ในการเรียกให้เฉพาะบาง user เท่านั้น

Roleสิทธิ์ทั่วไป
viewerSELECT, EXECUTE only
operatorSELECT, INSERT, UPDATE
adminALL PRIVILEGES

ป้องกัน SQL Injection ภายใน Procedure

MySQL Stored Procedure ไม่ปลอดภัยจาก SQL Injection หากมีการใช้ Dynamic SQL (PREPARE, EXECUTE)

ตัวอย่างที่เสี่ยง

SQL
SET @query = CONCAT('SELECT * FROM ', table_name);
PREPARE stmt FROM @query;
EXECUTE stmt;
  • หากตัวแปร table_name มาจาก user โดยตรง และไม่ผ่านการกรอง จะเสี่ยงต่อการโดนแฮกข้อมูล หรือรันคำสั่งอันตรายได้

แนวทางป้องกัน

  • หลีกเลี่ยงการใช้ Dynamic SQL หากไม่จำเป็น
  • ถ้าต้องใช้ ตรวจสอบ whitelist ของ input อย่างเข้มงวด

ตรวจสอบสิทธิ์ของผู้เรียกใช้ด้วย USER()

เราสามารถเขียน Logic ภายใน Procedure ให้ตรวจสอบว่าใครเป็นคนเรียก แล้วจำกัดสิทธิ์ได้ด้วย

SQL
IF USER() NOT LIKE 'admin@%' THEN
  SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = 'คุณไม่มีสิทธิ์ใช้งาน Procedure นี้';
END IF;

ข้อควรระวัง

  • หลีกเลี่ยงการใช้ GRANT ALL ON *.* แบบกว้างขวางเกินไป
  • อย่าฝังพาสเวิร์ด หรือข้อมูลสำคัญไว้ใน Procedure โดยตรง
  • หาก Procedure มีการอัปเดตข้อมูล ต้องตรวจสอบเงื่อนไขให้รัดกุมเสมอ
แชร์เรื่องนี้