SELECT * FROM users WHERE name=''; DROPTABLE users; --';user input escaped the stringSQL injection

การฉีด SQL

11 นาทีอ่านความปลอดภัย

การแทรก SQL อยู่ในอันดับต้นๆ ของรายการช่องโหว่ด้านความปลอดภัยบนเว็บทุกรายการนับตั้งแต่ช่วงปลายทศวรรษ 1990 การแก้ไข — การสืบค้นแบบกำหนดพารามิเตอร์ — เป็นที่รู้จัก เรียบง่าย และได้รับการแนะนำมานานกว่าสองทศวรรษ ยังมีการค้นพบช่องโหว่ SQLi ใหม่ทุกสัปดาห์ หมวดหมู่ยังคงมีอยู่เนื่องจากโหมดความล้มเหลวเป็นแบบละเอียด และค่าเริ่มต้นของกรอบงานจำนวนมากไม่สมบูรณ์

เนื้อหาบทความฉบับเต็มมีให้เป็นภาษาอังกฤษด้านล่าง

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

ข้อผิดพลาดแบบคลาสสิก

ตัวอย่างหนังสือเรียน แอปพลิเคชันยอมรับชื่อผู้ใช้จากแบบฟอร์มเข้าสู่ระบบและค้นหา:

query = "SELECT * FROM users WHERE username='" + input + "';"

If input is alice, แบบสอบถาม กลายเป็น:

SELECT * จากผู้ใช้ WHERE ชื่อผู้ใช้='alice';

Normal แต่ถ้า input คือ alice' หรือ '1'='1 ข้อความค้นหาจะกลายเป็น:

SELECT * FROM users WHERE username='alice' OR '1'='1';

The OR '1'='1' เป็นจริงเสมอ แบบสอบถามจะส่งกลับผู้ใช้ทุกคนในตาราง ด้วยเพย์โหลดที่ซับซ้อนมากขึ้น ผู้โจมตีสามารถถ่ายโอนข้อมูลตาราง แก้ไขบันทึก หรือขยายขนาดเพิ่มเติมได้

ประเภทการโจมตี

  • Classic / in-band SQLi. ผลลัพธ์ของการสืบค้นที่แทรกไว้จะปรากฏในการตอบสนองของแอปพลิเคชัน ผู้โจมตีอ่านข้อมูลโดยตรง
  • Blind SQLi. ไม่มีข้อมูลปรากฏในการตอบสนอง แต่พฤติกรรมของแอปพลิเคชันจะเปลี่ยนไปขึ้นอยู่กับว่าแบบสอบถามที่ฉีดจะส่งกลับค่าจริงหรือเท็จ ผู้โจมตีแยกข้อมูลทีละบิตโดยถามคำถามเช่น "เป็นอักขระตัวแรกของรหัสผ่านผู้ดูแลระบบ 'a' หรือไม่"
  • Time-based blind SQLi. ผู้โจมตีใช้ SLEEP() หรือเทียบเท่าเพื่อทำให้ฐานข้อมูลตอบสนองช้าเมื่อเงื่อนไขเป็นจริง แม้กระทั่งการขโมยข้อมูลช้าลง แต่จะทำงานเมื่อแอปพลิเคชันไม่ให้การตอบสนอง
  • SQLi นอกแบนด์ ผู้โจมตีหลอกฐานข้อมูลให้สร้างการเชื่อมต่อเครือข่ายภายนอก (การค้นหา DNS, คำขอ HTTP) ที่ทำให้ข้อมูลรั่วไหลไปยังเซิร์ฟเวอร์ที่พวกเขาควบคุม
  • SQL ลำดับที่สอง อินพุตที่เป็นอันตรายจะถูกเก็บไว้ในฐานข้อมูล ก่อน แล้วจึงอ่านและนำไปใช้ในแบบสอบถามอื่นอย่างไม่ปลอดภัยในภายหลัง การฉีดเกิดขึ้นในบริบทที่แตกต่างจากอินพุต

การแก้ไข: การสืบค้นแบบกำหนดพารามิเตอร์

วิธีที่ถูกต้องในการสืบค้นฐานข้อมูลด้วยการป้อนข้อมูลของผู้ใช้คือการใช้ตัวยึดตำแหน่งพารามิเตอร์ ไม่ต้องต่อสตริงเข้าด้วยกัน โดยพื้นฐานแล้วทุกภาษา:

// Python (psycopg2)
cursor.execute("เลือก * จากผู้ใช้ WHERE ชื่อผู้ใช้=%s", (อินพุต,))

//จาวา
stmt = conn.prepareStatement("SELECT * FROM users WHERE ชื่อผู้ใช้=?");
stmt.setString(1, อินพุต);

// จาวาสคริปต์ (pg)
client.query("SELECT * FROM users WHERE username=$1", [input]);

ไดรเวอร์จะส่ง SQL และค่าพารามิเตอร์แยกกัน ฐานข้อมูลไม่เคยสับสนข้อมูลอินพุตกับไวยากรณ์ SQL แม้ว่าอินพุตจะมีเครื่องหมายคำพูด อัฒภาค หรือส่วนคำสั่ง OR หรือไวยากรณ์ SQL อื่นๆ ก็จะถือว่าเป็นข้อมูล

ใช้งานได้ เป็นแนวทางที่แนะนำมาเป็นเวลาสองทศวรรษแล้ว ความท้าทายไม่ใช่อยู่ที่เทคนิค ทำให้แน่ใจว่าทุกแบบสอบถามในโค้ดเบสใช้มัน

ORM จะไม่ปลอดภัยโดยอัตโนมัติ

Object-Relational Mappers (ActiveRecord, Sequelize, Hibernate, SQLAlchemy) ใช้แบบสอบถามแบบกำหนดพารามิเตอร์ตามค่าเริ่มต้น ซึ่งดีมาก แต่ ORM ทุกตัวมีช่อง Escape:

  • raw()วิธีการ ที่ใช้สตริง SQL ที่กำหนดเอง
  • String-based ORDER BY clauses (ชื่อคอลัมน์ไม่สามารถกำหนดพารามิเตอร์ได้)
  • ชื่อตารางที่สอดแทรกเข้าไปในแบบสอบถาม
  • กระบวนงานที่เก็บไว้นั้นเอง concatenate

Modern SQLi bugs มักจะปรากฏในกรณี Edge เหล่านี้ ไม่ใช่ในพาธโค้ด CRUD หลัก

NoSQLject

รูปแบบนี้ไม่ได้มีลักษณะเฉพาะใน SQL MongoDB, Redis, ElasticSearch, GraphQL — ภาษาคิวรีใดๆ ที่ยอมรับอินพุตที่มีโครงสร้างจากผู้ใช้สามารถฉีดเข้าไปได้ หากอินพุตไม่ได้รับการตรวจสอบความถูกต้อง ตัวดำเนินการ $ne, $gt, $regex ของ MongoDB เป็นเวกเตอร์การฉีดทั่วไปเมื่อเพย์โหลด JSON ไม่ได้ตรวจสอบสคีมาอย่างเคร่งครัด

Defense ในเชิงลึก

Beyond ที่กำหนดพารามิเตอร์ แบบสอบถาม:

  • Least-privilege บัญชีฐานข้อมูล แอปพลิเคชันควรเชื่อมต่อกับผู้ใช้ที่มีสิทธิ์ที่จำเป็นเท่านั้น เส้นทางแบบอ่านอย่างเดียวใช้ข้อมูลรับรองแบบอ่านอย่างเดียว จำกัดความเสียหายเมื่อมีการใช้ประโยชน์จาก SQLi
  • การตรวจสอบอินพุต จำกัดอินพุตให้อยู่ในรูปแบบที่คาดหวัง มีประโยชน์แต่ยังไม่เพียงพอในตัวเอง
  • Web Application Firewalls (WAFs) รูปแบบตรงกับความพยายาม SQLi ที่ชัดเจน ผู้โจมตีสามารถข้ามผ่านได้แต่ทำให้ผู้โจมตีช้าลง
  • การวิเคราะห์แบบคงที่ เครื่องมือที่สแกนแหล่งที่มาสำหรับการสืบค้นที่ต่อด้วยสตริง IDE สมัยใหม่ตั้งค่าสถานะสิ่งเหล่านี้
  • Database hardening. ปิดการใช้งานคุณสมบัติที่ไม่จำเป็น (xp_cmdshell ใน MSSQL, ส่วนขยายที่เป็นอันตรายใน PostgreSQL)

Famous SQLi เหตุการณ์

  • 2007 TJX — บัตรเครดิต 94 ล้านใบ เริ่มต้นด้วยการประนีประนอม Wi-Fi แต่ SQLi ถูกใช้เพื่อถอนข้อมูลจากฐานข้อมูลภายใน
  • 2008 Heartland — บันทึกการ์ด 130 ล้านรายการ SQLi ให้สิทธิ์การเข้าถึงเครือข่ายครั้งแรก
  • 2012 LinkedIn — แฮชรหัสผ่าน 117 ล้านแฮชที่ถูกกรองผ่าน SQLi
  • 2017 Equifax — ช่องโหว่ Apache Struts แต่ SQLi เป็นส่วนหนึ่งของข้อมูลหลังการประนีประนอม การกรอง
  • ตลอดปี 2020s — การละเมิดเล็กๆ น้อยๆ นับไม่ถ้วนที่บริษัท SaaS, ไซต์อีคอมเมิร์ซ, หน่วยงานภาครัฐ

หมวดหมู่นี้มีอายุหลายสิบปีและยังคงมีชีวิตอยู่อย่างมาก

คำถามที่พบบ่อย

SQL injector ยังคงเป็นเรื่องปกติหรือไม่?
ใช่. 10 อันดับแรกของ OWASP แสดงรายการการฉีด (ซึ่งรวมถึง SQLi) ในสามหมวดหมู่อันดับแรกทุกปี โปรแกรมรางวัลจุดบกพร่องจะเห็นรายงาน SQLi ทุกสัปดาห์ เฟรมเวิร์กสมัยใหม่ได้ลดความถี่ในพาธโค้ดหลัก แต่แอปรุ่นเก่า เคส Edge และ Escape Hatch ORM ทำให้หมวดหมู่นี้ยังคงอยู่
Web Application Firewall สามารถหยุดการฉีด SQL ทั้งหมดได้หรือไม่
ไม่ WAF จับรูปแบบที่ชัดเจนและหยุดผู้โจมตีที่ไม่ซับซ้อน แต่ผู้โจมตีที่มีความรู้สามารถสร้างการเลี่ยงผ่านได้ — การเข้ารหัสที่แตกต่างกัน การแทรกความคิดเห็น และไวยากรณ์ SQL ทางเลือก WAF เป็นเลเยอร์ที่มีประโยชน์ ไม่ได้ใช้แทนการสร้างแบบสอบถามที่ปลอดภัยในแอปพลิเคชัน
HTTPS ป้องกันการฉีด SQL หรือไม่
ไม่ SQLi เป็นช่องโหว่ในชั้นแอปพลิเคชัน HTTPS ปกป้องข้อมูลระหว่างการส่งแต่ไม่ได้ตรวจสอบเนื้อหา ข้อมูลที่เป็นอันตรายของผู้โจมตีมาถึงผ่าน HTTPS เช่นเดียวกับคนอื่นๆ แอปพลิเคชันคือสิ่งที่ดำเนินการหรือไม่ประมวลผลได้อย่างปลอดภัย
ฐานข้อมูล NoSQL มีภูมิคุ้มกันหรือไม่
ไม่ NoSQLjection เป็นหมวดหมู่ของตัวเอง MongoDB, Elasticsearch และอื่นๆ ยอมรับอินพุตคิวรีที่มีโครงสร้างซึ่งสามารถจัดการได้หากไม่ได้รับการตรวจสอบอย่างเข้มงวด รูปแบบการป้องกันจะคล้ายกัน (ถือว่าอินพุตของผู้ใช้เป็นข้อมูล ไม่ใช่เป็นโครงสร้างแบบสอบถาม) แต่การโจมตีเฉพาะจะแตกต่างกัน
ฉันจะทดสอบไซต์ของตัวเองสำหรับการฉีด SQL ได้อย่างไร
เครื่องมือเช่น sqlmap ทำการทดสอบอัตโนมัติสำหรับรูปแบบ SQLi ทั่วไป OWASP ZAP และ Burp Suite มีสแกนเนอร์ด้วย สำหรับนักพัฒนา การตรวจสอบที่น่าเชื่อถือที่สุดคือการตรวจสอบโค้ดและการวิเคราะห์แบบคงที่สำหรับการสืบค้นที่ต่อสตริง ภารกิจการทดสอบการเจาะระบบรวมถึงการประเมิน SQLi โดยเฉพาะ
อธิบายการฉีด SQL: ช่องโหว่ของเว็บที่ไม่มีวันตาย