Sql Injection Part 1
เกริ่นนำ
โพสต์นี้พาแก้ Lab แรกของ PortSwigger Academy ในหัวข้อ SQL Injection vulnerability in WHERE clause allowing retrieval of hidden data
โจทย์ข้อนี้เป็นตัวอย่างพื้นฐานที่ดีมากสำหรับการเริ่มต้นทำความเข้าใจ SQL Injection เพราะจุดอ่อนอยู่ในพารามิเตอร์ category ที่ถูกนำไปประกอบเป็นคำสั่ง SQL โดยตรง
ภาพรวมของโจทย์คือ แอปพลิเคชันจะดึงข้อมูลสินค้าด้วย query ลักษณะนี้
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
ความหมายของ query นี้คือ:
เลือกสินค้าจากหมวด
Giftsและแสดงเฉพาะสินค้าที่ถูกปล่อยแล้ว (
released = 1)
เป้าหมายของเราคือทำให้ระบบแสดง สินค้าที่ซ่อนอยู่หรือยังไม่ถูกปล่อย ออกมาด้วย

วิเคราะห์ช่องทางรับค่า
เมื่อเปิดหน้า Lab ขึ้นมา เราจะเห็นว่าเว็บมีการกรองสินค้าตามหมวดหมู่ และเมื่อคลิกหมวดใดหมวดหนึ่ง ค่า category จะถูกส่งไปใน URL
ตัวอย่างเช่น เมื่อเลือกหมวด Clothing, shoes and accessories จะเห็นว่าหน้าเว็บเรียกข้อมูลผ่านพารามิเตอร์ category ชัดเจน

จุดนี้เป็นสัญญาณสำคัญว่า input จากผู้ใช้มีโอกาสถูกนำไปต่อเป็น SQL statement ทางฝั่ง backend โดยตรง
และเมื่อชื่อ Lab ระบุชัดว่าเป็น SQL Injection เราจึงเริ่มลองทดสอบด้วย payload พื้นฐานได้เลย
ทำความเข้าใจกับ payload ที่ใช้
แนวคิดของ SQL Injection ข้อนี้คือ “ปิด string เดิม แล้วเติมเงื่อนไขของเราเข้าไป” จากนั้นใช้ comment เพื่อตัดส่วนที่เหลือของ query ทิ้ง
payload ที่ใช้คือ
' OR '1'='1' --
มาดูทีละส่วน:
1) '
single quote ใช้สำหรับปิด string เดิมที่ระบบกำลังสร้างอยู่
ตัวอย่าง query ปกติ:
SELECT * FROM users WHERE firstname = 'สมชาย'
ถ้าเราควบคุมค่าภายใน string นี้ได้ เราก็อาจปิด string ก่อนเวลาอันควร แล้วต่อ logic ของเราเพิ่มเข้าไปได้
2) OR '1'='1'
นี่คือเงื่อนไขที่เป็นจริงเสมอ (true)
เพราะ '1'='1' ให้ผลเป็นจริงตลอด
3) --
ใช้สำหรับ comment ส่วนที่เหลือของ query ออกไป
ทำให้เงื่อนไขหลังจาก payload ของเราไม่ถูกประมวลผลต่อ
Query ถูกเปลี่ยนเป็นอะไร
เดิมระบบน่าจะสร้าง query ประมาณนี้
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
ถ้าเราฉีด payload เข้าไปใน category เช่น
Gifts' OR '1'='1' --
query จะกลายเป็น
SELECT * FROM products WHERE category = 'Gifts' OR '1'='1' -- ' AND released = 1
สิ่งที่เกิดขึ้นคือ:
เงื่อนไข
category = 'Gifts'ยังอยู่แต่มี
OR '1'='1'เพิ่มเข้ามา ทำให้เงื่อนไขรวมเป็นจริงส่วน
AND released = 1ถูก comment ทิ้ง
ผลลัพธ์คือระบบจะไม่จำกัดเฉพาะสินค้าที่ถูกปล่อยแล้วอีกต่อไป
ดังนั้นสินค้าที่ซ่อนอยู่หรือ unreleased products ก็ถูกดึงออกมาแสดงได้ด้วย
ทำไมถึง bypass ได้
หัวใจของข้อนี้อยู่ที่ตรรกะของเงื่อนไข
เดิม:
category = 'Gifts' AND released = 1
หลังฉีด:
category = 'Gifts' OR '1'='1'
เพราะ OR '1'='1' เป็นจริงเสมอ
เงื่อนไขทั้งหมดจึงมีแนวโน้มให้ผลเป็นจริงสำหรับทุกแถว ส่งผลให้ฐานข้อมูลคืนข้อมูลสินค้าทั้งหมดกลับมา
พูดง่าย ๆ คือเราเปลี่ยนจาก query ที่ “กรองข้อมูล” ให้กลายเป็น query ที่ “เปิดกว้าง” จนดึงข้อมูลเกินกว่าที่ระบบตั้งใจไว้
Payload ที่ใช้กับหน้า Lab
ตัวอย่าง payload ใน URL:
https://<lab-id>.web-security-academy.net/filter?category=Clothing,+shoes+and+accessories' OR '1'='1' --
เมื่อส่งค่าแบบนี้ไป หน้าเว็บจะแสดงสินค้าที่เดิมไม่ควรถูกมองเห็น และถือว่าแก้ Lab สำเร็จ

สรุปแนวคิดของโจทย์ข้อนี้
Lab นี้เป็นตัวอย่างคลาสสิกของ SQL Injection ใน WHERE clause โดยมีขั้นตอนคิดหลัก ๆ ดังนี้
สังเกตว่าหน้าเว็บรับค่า
categoryจาก URLเข้าใจว่าค่านี้น่าจะถูกนำไปประกอบ SQL query
ใช้ single quote เพื่อปิด string เดิม
เติมเงื่อนไขที่เป็นจริงเสมอด้วย
OR '1'='1'ใช้
--เพื่อ comment ส่วนที่เหลือของ queryทำให้ระบบดึงข้อมูลที่ไม่ควรแสดงออกมาได้
ข้อควรรู้เพิ่มเติม
แม้ข้อนี้จะเป็น Lab พื้นฐาน แต่แนวคิดเดียวกันสามารถต่อยอดไปสู่โจทย์ที่ซับซ้อนขึ้นได้ เช่น
การ bypass login
การดึงข้อมูลจากตารางอื่น
การใช้
UNION SELECTการ enumerate schema หรือชื่อคอลัมน์
การ blind SQL injection
ดังนั้นถ้าเข้าใจโจทย์ข้อนี้ดี จะเป็นฐานสำคัญสำหรับ Lab ขั้นถัดไป
แนวทางป้องกัน
สาเหตุของปัญหาไม่ได้อยู่ที่เครื่องหมาย ' หรือ -- เพียงอย่างเดียว แต่เกิดจากการนำ input ของผู้ใช้ไปต่อกับ SQL โดยตรง
แนวทางป้องกันที่ถูกต้องคือ:
ใช้ parameterized queries / prepared statements
ตรวจสอบและจำกัดรูปแบบ input
หลีกเลี่ยงการประกอบ SQL ด้วย string ต่อกันตรง ๆ
ใช้หลัก least privilege สำหรับบัญชีฐานข้อมูล
ตัวอย่างเชิงแนวคิด:
SELECT * FROM products WHERE category = ? AND released = 1
การส่งค่าผ่าน parameter จะช่วยแยกระหว่าง “ข้อมูล” กับ “คำสั่ง” ทำให้ input ของผู้ใช้ไม่ถูกตีความเป็น SQL code
บทส่งท้าย
สำหรับ Lab แรกของ PortSwigger ข้อนี้ถือว่าเหมาะมากสำหรับการเริ่มต้น เพราะทำให้เห็นภาพชัดว่า
SQL Injection ไม่จำเป็นต้องซับซ้อนเสมอไป แค่ input ถูกนำไปต่อ query แบบไม่ปลอดภัย ก็อาจทำให้เงื่อนไขในระบบถูกบิดจนดึงข้อมูลที่ไม่ควรเห็นออกมาได้
ถ้าจะสรุปโจทย์นี้ให้สั้นที่สุด:
ปิด string เดิม เติมเงื่อนไขที่เป็นจริงเสมอ แล้ว comment ส่วนที่เหลือทิ้ง
แค่นี้ก็เพียงพอที่จะทำให้ Lab นี้ผ่านได้แล้ว