ฉันรู้ว่า PreparedStatements หลีกเลี่ยง / ป้องกัน SQL Injection มันทำได้อย่างไร? แบบสอบถามฟอร์มสุดท้ายที่สร้างโดยใช้ PreparedStatements จะเป็นสตริงหรืออย่างอื่น?
ฉันรู้ว่า PreparedStatements หลีกเลี่ยง / ป้องกัน SQL Injection มันทำได้อย่างไร? แบบสอบถามฟอร์มสุดท้ายที่สร้างโดยใช้ PreparedStatements จะเป็นสตริงหรืออย่างอื่น?
คำตอบ:
ปัญหาเกี่ยวกับการฉีด SQL คืออินพุตของผู้ใช้ถูกใช้เป็นส่วนหนึ่งของคำสั่ง SQL ด้วยการใช้คำสั่งที่เตรียมไว้คุณสามารถบังคับให้อินพุตของผู้ใช้ถูกจัดการเป็นเนื้อหาของพารามิเตอร์ (ไม่ใช่เป็นส่วนหนึ่งของคำสั่ง SQL)
แต่ถ้าคุณไม่ได้ใช้อินพุตของผู้ใช้เป็นพารามิเตอร์สำหรับคำสั่งที่คุณเตรียมไว้ แต่สร้างคำสั่ง SQL ของคุณโดยการรวมสตริงเข้าด้วยกันคุณยังคงเสี่ยงต่อการแทรก SQLแม้ว่าจะใช้คำสั่งที่เตรียมไว้ก็ตาม
พิจารณาสองวิธีในการทำสิ่งเดียวกัน:
PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();
หรือ
PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();
หาก "ผู้ใช้" มาจากอินพุตของผู้ใช้และอินพุตของผู้ใช้คือ
Robert'); DROP TABLE students; --
จากนั้นในกรณีแรกคุณจะถูก hosed ประการที่สองคุณจะปลอดภัยและ Little Bobby Tables จะได้รับการลงทะเบียนสำหรับโรงเรียนของคุณ
เพื่อให้เข้าใจว่า PreparedStatement ป้องกัน SQL Injection อย่างไรเราจำเป็นต้องเข้าใจขั้นตอนของการเรียกใช้ SQL Query
1. คอมไพล์เฟส. 2. ขั้นตอนการดำเนินการ
เมื่อใดก็ตามที่เอ็นจินเซิร์ฟเวอร์ SQL ได้รับแบบสอบถามจะต้องผ่านขั้นตอนด้านล่าง
ขั้นตอนการแยกวิเคราะห์และการปรับมาตรฐาน: ในระยะนี้คิวรีถูกตรวจสอบสำหรับไวยากรณ์และความหมาย ตรวจสอบว่ามีตารางอ้างอิงและคอลัมน์ที่ใช้ในแบบสอบถามหรือไม่ นอกจากนี้ยังมีงานอื่น ๆ อีกมากมายที่ต้องทำ แต่จะไม่ลงรายละเอียด
ขั้นตอนการรวบรวม: ในระยะนี้คำหลักที่ใช้ในการสืบค้นเช่นเลือกจากที่อื่น ๆ จะถูกแปลงเป็นรูปแบบที่เครื่องเข้าใจได้ นี่คือขั้นตอนที่มีการตีความแบบสอบถามและตัดสินใจดำเนินการที่เกี่ยวข้อง นอกจากนี้ยังมีงานอื่น ๆ อีกมากมายที่ต้องทำ แต่จะไม่ลงรายละเอียด
แผนการเพิ่มประสิทธิภาพการสืบค้น: ในขั้นตอนนี้ตัดสินใจถูกสร้างขึ้นเพื่อค้นหาวิธีการดำเนินการสืบค้น พบจำนวนวิธีที่สามารถดำเนินการสืบค้นและต้นทุนที่เกี่ยวข้องกับวิธีการดำเนินการค้นหาแต่ละวิธี เลือกแผนการที่ดีที่สุดสำหรับการดำเนินการค้นหา
แคช: แผนที่ดีที่สุดที่เลือกในแผนการเพิ่มประสิทธิภาพการค้นหาจะถูกเก็บไว้ในแคชดังนั้นเมื่อใดก็ตามที่มีการสืบค้นเดียวกันในครั้งต่อไปก็ไม่จำเป็นต้องผ่านขั้นตอนที่ 1 ระยะที่ 2 และระยะที่ 3 อีก เมื่อมีการสืบค้นในครั้งต่อไประบบจะตรวจสอบโดยตรงใน Cache และหยิบขึ้นมาจากที่นั่นเพื่อดำเนินการ
เฟสการดำเนินการ:
ในระยะนี้แบบสอบถามที่ให้มาจะถูกดำเนินการและข้อมูลจะถูกส่งกลับไปยังผู้ใช้เป็นResultSet
วัตถุ
PreparedStatements ไม่ใช่เคียวรี SQL ที่สมบูรณ์และมีตัวยึดตำแหน่งซึ่งในขณะรันจะถูกแทนที่ด้วยข้อมูลที่ผู้ใช้ให้มาจริง
เมื่อใดก็ตามที่ PreparedStatment ที่มีตัวยึดตำแหน่งถูกส่งผ่านไปยังเอ็นจิน SQL Server จะผ่านขั้นตอนด้านล่าง
อัปเดตผู้ใช้ตั้งชื่อผู้ใช้ =? และรหัสผ่าน =? id = ที่ไหน
ข้อความค้นหาข้างต้นจะได้รับการแยกวิเคราะห์รวบรวมด้วยตัวยึดตำแหน่งเป็นการปฏิบัติพิเศษปรับให้เหมาะสมและรับแคช แบบสอบถามในขั้นตอนนี้ได้รับการรวบรวมและแปลงในรูปแบบที่เครื่องเข้าใจได้แล้ว ดังนั้นเราจึงสามารถพูดได้ว่า Query ที่เก็บไว้ในแคชเป็น Pre-Compiled และต้องแทนที่ตัวยึดตำแหน่งด้วยข้อมูลที่ผู้ใช้ให้มาเท่านั้น
ขณะนี้อยู่ในช่วงรันไทม์เมื่อมีข้อมูลที่ผู้ใช้ให้มาการสืบค้นที่รวบรวมไว้ล่วงหน้าจะถูกดึงขึ้นมาจากแคชและตัวยึดตำแหน่งจะถูกแทนที่ด้วยข้อมูลที่ผู้ใช้ให้มา
(โปรดจำไว้ว่าหลังจากแทนที่ตัวยึดตำแหน่งด้วยข้อมูลผู้ใช้แบบสอบถามสุดท้ายจะไม่ถูกคอมไพล์ / ตีความอีกและเอ็นจิน SQL Server ถือว่าข้อมูลผู้ใช้เป็นข้อมูลที่บริสุทธิ์ไม่ใช่ SQL ที่ต้องแยกวิเคราะห์หรือรวบรวมอีกครั้งนั่นคือความสวยงามของ PreparedStatement )
หากแบบสอบถามไม่ต้องผ่านขั้นตอนการรวบรวมอีกครั้งข้อมูลใด ๆ ที่ถูกแทนที่ในตัวยึดตำแหน่งจะถือว่าเป็นข้อมูลที่บริสุทธิ์และไม่มีความหมายต่อกลไกของ SQL Server และจะเรียกใช้แบบสอบถามโดยตรง
หมายเหตุ: เป็นเฟสการคอมไพล์หลังจากขั้นตอนการแยกวิเคราะห์ที่เข้าใจ / ตีความโครงสร้างแบบสอบถามและให้พฤติกรรมที่มีความหมาย ในกรณีของ PreparedStatement คิวรีจะถูกคอมไพล์เพียงครั้งเดียวและคิวรีที่คอมไพล์แล้วจะถูกหยิบขึ้นมาตลอดเวลาเพื่อแทนที่ข้อมูลผู้ใช้และดำเนินการ
เนื่องจากคุณลักษณะการรวบรวมครั้งเดียวของ PreparedStatement จึงไม่มีการโจมตี SQL Injection
คุณสามารถรับคำอธิบายโดยละเอียดพร้อมตัวอย่างได้ที่นี่: https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html
SQL ที่ใช้ใน PreparedStatement ถูกคอมไพล์ไว้ล่วงหน้าบนไดรเวอร์ จากจุดนั้นพารามิเตอร์จะถูกส่งไปยังไดรเวอร์เป็นค่าตัวอักษรและไม่ใช่ส่วนที่เรียกใช้งานได้ของ SQL ดังนั้นจึงไม่สามารถฉีด SQL โดยใช้พารามิเตอร์ได้ ผลข้างเคียงที่เป็นประโยชน์อีกประการหนึ่งของ PreparedStatements (precompilation + ส่งเฉพาะพารามิเตอร์) คือประสิทธิภาพที่ดีขึ้นเมื่อเรียกใช้คำสั่งหลายครั้งแม้จะมีค่าต่างกันสำหรับพารามิเตอร์ (สมมติว่าไดรเวอร์รองรับ PreparedStatements) เนื่องจากไดรเวอร์ไม่จำเป็นต้องทำการแยกวิเคราะห์และรวบรวม SQL เวลาที่พารามิเตอร์เปลี่ยนไป
ฉันเดาว่ามันจะเป็นสตริง แต่พารามิเตอร์อินพุตจะถูกส่งไปยังฐานข้อมูลและจะใช้การส่ง / การแปลงที่เหมาะสมก่อนสร้างคำสั่ง SQL จริง
เพื่อเป็นตัวอย่างอาจลองดูว่าแคสต์ / คอนเวอร์ชั่นใช้ได้หรือไม่
ถ้ามันใช้งานได้มันสามารถสร้างคำสั่งสุดท้ายจากมันได้
SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))
ลองใช้ตัวอย่างคำสั่ง SQL ที่ยอมรับพารามิเตอร์ตัวเลข
ตอนนี้ให้ลองส่งตัวแปรสตริง (พร้อมเนื้อหาตัวเลขที่ยอมรับเป็นพารามิเตอร์ตัวเลข) มันทำให้เกิดข้อผิดพลาดหรือไม่?
ตอนนี้ให้ลองส่งตัวแปรสตริง (ด้วยเนื้อหาที่ไม่สามารถยอมรับได้เป็นพารามิเตอร์ตัวเลข) ดูว่าเกิดอะไรขึ้น?
งบที่เตรียมไว้มีความปลอดภัยมากขึ้น มันจะแปลงพารามิเตอร์เป็นชนิดที่ระบุ
ตัวอย่างเช่นstmt.setString(1, user);
จะแปลงuser
พารามิเตอร์เป็นสตริง
สมมติว่าพารามิเตอร์มีสตริง SQL ที่มีคำสั่งปฏิบัติการ : การใช้ที่เตรียมไว้จะไม่อนุญาตให้ทำเช่นนั้น
มันจะเพิ่ม metacharacter (aka auto conversion) เข้าไป
ทำให้ปลอดภัยมากขึ้น
SQL injection: เมื่อผู้ใช้มีโอกาสป้อนข้อมูลบางอย่างที่อาจเป็นส่วนหนึ่งของคำสั่ง sql
ตัวอย่างเช่น:
ข้อความค้นหาสตริง =“ INSERT INTO students VALUES ('” + user +“')”
เมื่อผู้ใช้ป้อน“ Robert '); DROP TABLE นักเรียน; -” เป็นอินพุตทำให้เกิดการแทรก SQL
คำสั่งที่เตรียมไว้ป้องกันสิ่งนี้อย่างไร?
ข้อความค้นหาสตริง =“ INSERT INTO students VALUES ('” +“: name” +“')”
parameters.addValue (“ ชื่อ” ผู้ใช้);
=> เมื่อผู้ใช้ป้อนข้อมูลอีกครั้ง“ Robert '); DROP TABLE นักเรียน; -“ สตริงอินพุตถูกคอมไพล์ไว้ล่วงหน้าบนไดรเวอร์เป็นค่าตัวอักษรและฉันเดาว่ามันอาจจะถูกแคสต์ดังนี้:
CAST ( 'โรเบิร์ต'); DROP TABLE นักเรียน; - 'เป็น varchar (30))
ดังนั้นในตอนท้ายสตริงจะถูกแทรกเป็นชื่อในตารางอย่างแท้จริง
http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/
CAST(‘Robert’);
จากCAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))
จะแตกแล้วมันจะวางตารางต่อไปหากเป็นเช่นนั้น มันหยุดการฉีดดังนั้นฉันเชื่อว่าตัวอย่างยังไม่สมบูรณ์เพียงพอที่จะอธิบายสถานการณ์
PreparedStatement:
1) การคอมไพล์ล่วงหน้าและการแคชฝั่ง DB ของคำสั่ง SQL นำไปสู่การดำเนินการโดยรวมที่เร็วขึ้นและความสามารถในการนำคำสั่ง SQL เดิมกลับมาใช้ใหม่เป็นชุด
2) การป้องกันการโจมตีด้วยการฉีด SQL โดยอัตโนมัติโดยการหลีกเลี่ยงเครื่องหมายคำพูดและอักขระพิเศษอื่น ๆ ในตัว โปรดทราบว่าสิ่งนี้ต้องการให้คุณใช้เมธอด PreparedStatement setXxx () เพื่อตั้งค่า
ตามที่อธิบายไว้ในโพสต์นี้ไฟล์PreparedStatement
เพียงอย่างเดียวไม่ช่วยให้คุณถ้าคุณยังคงเชื่อมโยง Strings
ตัวอย่างเช่นผู้โจมตีโกงหนึ่งคนยังสามารถทำสิ่งต่อไปนี้ได้:
ไม่เพียง แต่ SQL เท่านั้น แต่แม้แต่ JPQL หรือ HQL ก็สามารถถูกบุกรุกได้หากคุณไม่ได้ใช้พารามิเตอร์การโยง
บรรทัดล่างสุดคุณไม่ควรใช้การต่อสายอักขระเมื่อสร้างคำสั่ง SQL ใช้ API เฉพาะเพื่อจุดประสงค์นั้น:
ใน Prepared Statements ผู้ใช้ถูกบังคับให้ป้อนข้อมูลเป็นพารามิเตอร์ หากผู้ใช้ป้อนคำสั่งที่มีช่องโหว่เช่น DROP TABLE หรือ SELECT * FROM USERS ข้อมูลจะไม่ได้รับผลกระทบเนื่องจากจะถือว่าเป็นพารามิเตอร์ของคำสั่ง SQL