arrow_backกลับไปที่บันทึกภาคสนาม
WEB SECURITY เผยแพร่แล้ว 8 Aug 2026

SQL Injection คืออะไร และจะป้องกันได้อย่างไร

การวิเคราะห์เชิงปฏิบัติของการโจมตี SQL injection พร้อมตัวอย่างจริงและวิธีแก้ไขที่ได้ผลจริงในโค้ดโปรดัคชัน

SQL injection มีมาตั้งแต่ปลายยุค 1990s และยังคงเป็นวิธีหนึ่งที่พบได้บ่อยที่สุดสำหรับการบุกรุกเว็บแอปพลิเคชัน ข้อบกพร่องนี้ง่ายเกินไป: ผู้โจมตีส่งข้อมูลซึ่งถูกตีความว่าเป็นโค้ด SQL แทนที่จะเป็นข้อมูลธรรมดา และฐานข้อมูลจะทำสิ่งที่ไม่ควรจะทำ

วิธีการโจมตีที่แท้จริง

ลองนึกภาพฟอร์มล็อกอินที่สร้างคำขอแบบนี้ใน PHP:

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

หากแอปพลิเคชันไม่ตัดสินใจกลั่นกรองข้อมูลเข้า ผู้โจมตีจะพิมพ์ admin' -- ลงในช่องชื่อผู้ใช้ คำขอจะกลายเป็น:

SELECT * FROM users WHERE username = 'admin' --' AND password = ''

-- ทำให้ส่วนที่เหลือของบรรทัดกลายเป็นหมายเหตุ จึงไม่มีการตรวจสอบรหัสผ่าน นี่คือการบายพาสการยืนยันตัวตนแบบคลาสสิก ผู้โจมตียังใช้ UNION SELECT เพื่อดึงข้อมูลจากตารางอื่น หรือเรียงคำขอหลายคำด้วยเซมิโคลอนเพื่อรันสิ่งที่ทำลายล้างเช่น DROP TABLE users; ถ้าไดรเวอร์อนุญาตให้ใช้คำสั่งหลายคำ

มีอีกวิธีหนึ่งคือ blind SQL injection ซึ่งแอปพลิเคชันไม่แสดงผลลัพธ์คำขอโดยตรง ผู้โจมตีสรุปข้อมูลผ่านการวัดเวลา (SLEEP(5) ใน MySQL) หรือการตอบสนองแบบบูลีน (หน้าเว็บทำงานต่างกันเมื่อเงื่อนไขเป็นจริงหรือเท็จหรือไม่) เครื่องมือเช่น sqlmap ทำให้กระบวนการสกัดข้อมูลอัตโนมัติเมื่อพบพารามิเตอร์ที่มีช่องโหว่

เหตุใดการเชื่อมต่อสตริงจึงเป็นปัญหาหลัก

ทุกรูปแบบของการโจมตีนี้กลับไปสู่สิ่งเดียว: การผสมโค้ดและข้อมูลในสตริงเดียวกัน ฐานข้อมูลไม่สามารถแยกแยะความแตกต่างระหว่างค่าที่ถูกต้องและไวยากรณ์ที่ถูกฉีดเข้ามา เพราะมันเข้ามาผ่านช่องทางเดียวกัน การหลุดออกจากการอ้างอิงช่วยได้ในบางกรณี แต่มันอ่อนแอ — การเข้ารหัสที่แตกต่างกัน การฉีดลำดับที่สอง (ข้อมูลเก็บไว้ครั้งหนึ่ง จากนั้นนำมาใช้อีกครั้งอย่างไม่ปลอดภัย) และความแปลกประหลาดเฉพาะไดรเวอร์ทั้งหมดสร้างเส้นทางบายพาส การหลุดออกจากการอ้างอิงเป็นแพตช์ไม่ใช่การแก้ไข

คำขอแบบพารามิเตอร์เป็นการแก้ไขที่แท้จริง

การแก้ไขคือการแยกโค้ด SQL ออกจากข้อมูลผู้ใช้ที่ระดับไดรเวอร์ โดยใช้คำขอแบบพารามิเตอร์ (เรียกอีกอย่างว่า prepared statements) ฐานข้อมูลจะได้รับโครงสร้างคำขอก่อน จากนั้นจึงผูกค่าต่อมา ดังนั้นข้อมูลเข้าจากผู้ใช้จึงไม่สามารถเปลี่ยนความหมายของคำขอได้

ใน Python ด้วย psycopg2:

cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))

ใน Node.js ด้วย mysql2:

connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);

ใน Java ด้วย JDBC:

PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);

สังเกตรูปแบบ: ตัวยึดตำแหน่ง (%s, ?) สำรองตำแหน่งสำหรับข้อมูล และค่าจริงจะถูกส่งแยกต่างหาก ไม่มีการเชื่อมต่อสตริง ไม่จำเป็นต้องหลุดออกจากการอ้างอิงด้วยตัวเอง วิธีนี้ใช้ได้กับส่วนใหญ่ของคำขอที่คุณจะเขียนในแอปพลิเคชันปกติ

จะเป็นอย่างไรกับชื่อตารางหรือชื่อคอลัมน์ที่มีการเปลี่ยนแปลง?

การกำหนดพารามิเตอร์จัดการกับค่า แต่ไม่จัดการกับตัวระบุ — คุณไม่สามารถผูกชื่อตารางเป็นพารามิเตอร์ได้ ถ้าแอปพลิเคชันของคุณต้องเลือกตารางแบบไดนามิก (드물고มักจะเป็นเครื่องหมายการออกแบบที่ไม่ดี) ให้ใช้รายชื่อที่อนุญาตเพียงค่าเสีย ที่กำหนดแทนการไว้วางใจข้อมูลเข้าจากผู้ใช้โดยตรง:

allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
    raise ValueError("Invalid table")

ไม่ต้องสร้างชื่อตัวระบุผ่านการจัดรูปแบบสตริงจากข้อมูลเข้าจากผู้ใช้ แม้จะหลุดออกจากการอ้างอิง

ปกป้องชั้นนอกเหนือจากคำขอนั้นเอง

คำขอแบบพารามิเตอร์เป็นการควบคุมหลัก แต่มีสิ่งอื่นๆ อีกสองสามอย่างที่สำคัญ:

  • สิทธิ์ที่จำกัดที่สุดบนบัญชีฐานข้อมูล บัญชีผู้ใช้ DB ของแอปพลิเคชันไม่ควรมี DROP, ALTER หรือการเข้าถึงสกีมาที่ไม่เกี่ยวข้อง หากการฉีดเข้ามาหลุดออกไป สิทธิ์ที่จำกัดจะจำกัดความเสียหาย
  • ORM ช่วยให้ปลอดภัยตามค่าเริ่มต้น Django ORM, SQLAlchemy และ Hibernate ทั้งหมดกำหนดพารามิเตอร์คำขออัตโนมัติเมื่อคุณใช้วิธีการสร้างคำขอมาตรฐาน ความเสี่ยงกลับมาเมื่อผู้พัฒนาโยนตัวเองลงในโค้ด SQL ดิบหรือใช้การเรียก .extra()/text() ด้วยการแทรกสตริง — ดังนั้นจึงควรตรวจสอบจุดเหล่านั้นโดยเฉพาะ
  • การตรวจสอบข้อมูลเข้าเป็นเลเยอร์ทุติยภูมิ ไม่ใช่การแทนที่ การตรวจสอบว่าช่องอีเมลดูเหมือนอีเมลหรือไม่เป็นวิธีปฏิบัติที่ดี แต่มันจะไม่หยุดการฉีดเข้ามาด้วยตัวเอง — ผู้โจมตีค้นหาเพย์โลดที่สร้างสรรค์ที่ยังคงผ่านการตรวจสอบที่ไม่เข้มงวด
  • WAF สามารถจับรูปแบบการโจมตีที่ทราบได้ แต่มันเป็นเลเยอร์การตรวจจับ ไม่ใช่การแก้ไขสำหรับโค้ดพื้นฐาน

ทดสอบโค้ดของคุณเองสำหรับข้อบกพร่องนี้

เรียกคำขอของคุณผ่านเครื่องมือวิเคราะห์แบบสแตติก (Bandit สำหรับ Python, Semgrep พร้อมชุดกฎ SQL injection) เป็นส่วนหนึ่งของ CI สำหรับการทดสอบด้วยตนเอง ลองฉีดเข้าไปอัญประกาศเดียว (') ลงในทุกช่องข้อมูลเข้าและดูข้อความข้อผิดพลาด SQL ที่รั่วไหลในการตอบกลับ — นั่นมักจะเป็นสัญญาณแรกว่าคำขอไม่ได้รับการกำหนดพารามิเตอร์

ถ้าคุณต้องการเจาะลึกเรื่องนี้ Korra Studio's Web Security track ครอบคลุมการฉีดเข้ามาควบคู่ไป กับ XSS และการบายพาส auth และส่วน Databases อธิบายรูปแบบการออกแบบคำขอที่หลีกเลี่ยงคลาสข้อบกพร่องนี้อย่างสิ้นเชิง

เขียนด้วยความช่วยเหลือของ AI ตรวจสอบและเผยแพร่โดย Michal Pilch (CISSP), Korra Studio

พร้อมที่จะไปต่อหรือไม่

นี่คือบันทึกหนึ่งจากฐานความรู้ของ Korra Studio — แพลตฟอร์มจับคู่หัวข้อแต่ละหัวข้อกับการฝึกสอนแบบ 1-to-1

เริ่มใช้งานฟรีarrow_forward