ในการออกแบบ API สำหรับดึงข้อมูลจำนวนมาก เช่น การใช้งานร่วมกับ Library อย่าง DataTables หรือ Tabulator นักพัฒนามักจะเน้นความสะดวกโดยการรับพารามิเตอร์ sort หรือ orderBy จาก Client โดยตรง แต่ถ้าหากขาดการตรวจสอบที่รัดกุม หรือไม่มีการใช้ Allowed List สิ่งนี้จะกลายเป็นช่องโหว่ที่อันตรายอย่างยิ่ง
บทความนี้จะเจาะลึกถึง 2 ประเด็นหลักที่มักถูกมองข้าม: การไม่ตรวจสอบ Data Type และการไม่จำกัด Allowed List ของฟิลด์ที่อนุญาตให้เรียงลำดับ
ช่องโหว่จากการไม่เช็ค Allowed List (Column Whitelisting)
เมื่อ API รับค่าชื่อ Column มาจาก User โดยตรงเพื่อนำไปใส่ใน Query เช่น SELECT * FROM users ORDER BY {user_input} หากไม่มีการตรวจสอบว่า Column นั้น “อนุญาต” ให้เข้าถึงหรือไม่ จะเกิดปัญหาดังนี้
การรั่วไหลของข้อมูล
Hacker สามารถเดาชื่อ Column ที่เป็นความลับได้ เช่น password_hash, secret_token หรือ internal_remark แม้ว่า API จะไม่ได้ Return ค่าเหล่านี้ออกไปตรง ๆ แต่การสั่ง ORDER BY password_hash จะทำให้ลำดับของข้อมูลเปลี่ยนไป ซึ่งช่วยให้ผู้ไม่หวังดีใช้เทคนิค Inference เพื่อสุ่มหาข้อมูลใน Database ได้
SQL Injection
นี่คือความเสี่ยงที่ร้ายแรงที่สุด หาก Code ของคุณใช้การต่อ String แทนการใช้ Parameterized Query Hacker อาจใส่คำสั่งอันตรายต่อท้าย เช่น
?sort=id; DROP TABLE users;--?sort= (CASE WHEN (SELECT ASCII (SUBSTR (password,1,1)) FROM users WHERE id=1) =97 THEN name ELSE id END)
อันตรายจากการไม่เช็ค Data Type ของการ Sort
บางครั้งเราอาจจะเช็คชื่อ Column แล้ว แต่ลืมตรวจสอบ Type หรือ Direction ของการเรียงลำดับ
การทำ Denial of Service ทางทรัพยากร
หาก Hacker ส่งการ Sort ในฟิลด์ที่ไม่ได้ทำ Index ไว้ และฐานข้อมูลมีขนาดใหญ่มาก การสั่ง Sort จะบังคับให้ Database ทำ “Full Table Scan” และใช้ CPU / Memory มหาศาลในการจัดเรียงข้อมูลใหม่ทุกครั้งที่เรียก API ส่งผลให้ระบบช้าลงหรือล่มได้
Logic Errors
ในระดับ Application หากเราไม่เช็คว่าค่าที่ส่งมาเป็น String, Number หรือ Boolean อาจเกิด Error ในระดับ Runtime ที่ทำให้ Application Crash หรือเผยแพร่ Stack Trace ที่เป็นข้อมูลภายในของ Server ออกมา
แนวทางการป้องกัน
เพื่อความปลอดภัยสูงสุดในการทำ List Data API ควรปฏิบัติดังนี้
ใช้ Allowed List เสมอ
กำหนดให้ชัดเจนว่า Column ไหนบ้างที่อนุญาตให้ Client สั่ง Sort ได้
// ตัวอย่าง Logic การตรวจสอบ
const allowedSortFields = ['id', 'username', 'created_at'];
const sortBy = allowedSortFields.includes (request.query.sort) ? request.query.sort : 'id'; // ค่า Default
ตรวจสอบทิศทาง
อย่ารับค่า ASC หรือ DESC มาจาก String โดยตรงโดยไม่มีการตรวจสอบ
const direction = request.query.dir.toUpperCase () === 'DESC' ? 'DESC' : 'ASC';
ปิดการแสดง Error ของ Database
อย่าส่ง Error Message ดิบ ๆ จาก SQL กลับไปให้ User เพราะจะเป็นการบอกโครงสร้าง Table ให้ Hacker ทราบ
ตรวจสอบ Index
ตรวจสอบให้แน่ใจว่า Column ที่เปิดให้ Sort ได้นั้นมีการทำ Index ไว้ใน Database เพื่อป้องกันปัญหา Performance
สรุป
- การปล่อยให้ Client ควบคุมการ Query ได้อย่างอิสระโดยไม่มีการ Filter คือการเปิดประตูบ้านทิ้งไว้ การใช้ Allowed List คือกำแพงด่านแรกที่สำคัญที่สุดในการปกป้องข้อมูลและเสถียรภาพของระบบครับ
- การตรวจสอบจริง ๆ แล้วต้องมีทั้งคู่
- frontend เช็คตั้งแต่ต้นทาง
- backend เพราะเป็น api อาจจะโดน call ตรง ๆ ผ่านโปรแกรมอื่นที่ไม่ใช้ frontend ของเราก็ได้
อ่านเพิ่มเติม