PreparedStatement จะหลีกเลี่ยงหรือป้องกันการแทรก SQL ได้อย่างไร


122

ฉันรู้ว่า PreparedStatements หลีกเลี่ยง / ป้องกัน SQL Injection มันทำได้อย่างไร? แบบสอบถามฟอร์มสุดท้ายที่สร้างโดยใช้ PreparedStatements จะเป็นสตริงหรืออย่างอื่น?


3
ในทางเทคนิคข้อมูลจำเพาะ JDBC ไม่ได้ยืนยันว่าไม่มีข้อบกพร่องในการฉีด SQL ฉันไม่รู้ว่ามีไดรฟ์ใดบ้างที่ได้รับผลกระทบ
— Tom Hawtin - แท็กไลน์

3
@Jayesh ฉันขอแนะนำให้เพิ่มเนื้อหาบล็อกของคุณเป็นคำตอบที่นี่ คำตอบส่วนใหญ่เป็นเพียงการบอกความแตกต่าง b / w การสร้างแบบสอบถาม SQL แบบไดนามิกและ stmt ที่เตรียมไว้ พวกเขาไม่ได้ระบุถึงปัญหาที่ว่าเหตุใดข้อความที่เตรียมไว้จึงทำงานได้ดีกว่าที่บล็อกของคุณทำ
— Pavan Manjunath

1
เพิ่มเป็นคำตอบฉันหวังว่ามันจะช่วยได้
— Jayesh

คำตอบ:


78

ปัญหาเกี่ยวกับการฉีด SQL คืออินพุตของผู้ใช้ถูกใช้เป็นส่วนหนึ่งของคำสั่ง SQL ด้วยการใช้คำสั่งที่เตรียมไว้คุณสามารถบังคับให้อินพุตของผู้ใช้ถูกจัดการเป็นเนื้อหาของพารามิเตอร์ (ไม่ใช่เป็นส่วนหนึ่งของคำสั่ง SQL)

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


1
แน่นอน แต่คุณยังสามารถฮาร์ดโค้ดพารามิเตอร์บางส่วนหรือทั้งหมดได้
— Tangens

16
ตัวอย่างโปรด - แต่ถ้าคุณไม่ใช้อินพุตของผู้ใช้เป็นพารามิเตอร์สำหรับคำสั่งที่เตรียมไว้ แต่สร้างคำสั่ง SQL ของคุณโดยการรวมสตริงเข้าด้วยกันคุณยังคงเสี่ยงต่อการแทรก SQL แม้ว่าจะใช้คำสั่งที่เตรียมไว้ก็ตาม
— david blaine

4
FWIW คำสั่งที่เตรียมไว้ไม่ใช่สิ่งที่ JDBC แต่เป็นสิ่งที่ SQL คุณสามารถจัดเตรียมและดำเนินการคำสั่งที่เตรียมไว้ได้จากภายในคอนโซล SQL PreparedStatement รองรับเพียงแค่จากภายใน JDBC
— beldaz

198

พิจารณาสองวิธีในการทำสิ่งเดียวกัน:

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 จะได้รับการลงทะเบียนสำหรับโรงเรียนของคุณ


8
ดังนั้นถ้าฉันเข้าใจถูกคำถามในตัวอย่างที่สองซึ่งจะถูกเรียกใช้จริงจะเป็น: INSERT INTO student VALUES ("Robert '); DROP TABLE students; -") - หรืออย่างน้อยก็อย่างนั้น นี่คือเรื่องจริง?
— สูงสุด

18
ไม่ในตัวอย่างแรกคุณจะได้รับคำสั่งนั้น ในรายการที่สองจะแทรก "Robert '); DROP TABLE students; -" ลงในตารางผู้ใช้
— Paul Tomblin

3
นั่นคือสิ่งที่ฉันหมายถึงในตัวอย่างที่สอง (อันที่ "ปลอดภัย") สตริงRobert '); DROP TABLE นักเรียน; -จะถูกบันทึกลงในฟิลด์ในตารางนักเรียน ฉันเขียนอย่างอื่นหรือไม่? ;)
— สูงสุด

7
ขออภัยคำพูดซ้อนเป็นสิ่งที่ฉันพยายามหลีกเลี่ยงเนื่องจากความสับสนเช่นนี้ นั่นเป็นเหตุผลที่ฉันชอบ PreparedStatements ที่มีพารามิเตอร์
— Paul Tomblin

59
โต๊ะบ๊อบบี้ตัวน้อย XD Great reference
— Amalgovinus

129

เพื่อให้เข้าใจว่า PreparedStatement ป้องกัน SQL Injection อย่างไรเราจำเป็นต้องเข้าใจขั้นตอนของการเรียกใช้ SQL Query

1. คอมไพล์เฟส. 2. ขั้นตอนการดำเนินการ

เมื่อใดก็ตามที่เอ็นจินเซิร์ฟเวอร์ SQL ได้รับแบบสอบถามจะต้องผ่านขั้นตอนด้านล่าง

ขั้นตอนการดำเนินการสืบค้น

  1. ขั้นตอนการแยกวิเคราะห์และการปรับมาตรฐาน: ในระยะนี้คิวรีถูกตรวจสอบสำหรับไวยากรณ์และความหมาย ตรวจสอบว่ามีตารางอ้างอิงและคอลัมน์ที่ใช้ในแบบสอบถามหรือไม่ นอกจากนี้ยังมีงานอื่น ๆ อีกมากมายที่ต้องทำ แต่จะไม่ลงรายละเอียด

  2. ขั้นตอนการรวบรวม: ในระยะนี้คำหลักที่ใช้ในการสืบค้นเช่นเลือกจากที่อื่น ๆ จะถูกแปลงเป็นรูปแบบที่เครื่องเข้าใจได้ นี่คือขั้นตอนที่มีการตีความแบบสอบถามและตัดสินใจดำเนินการที่เกี่ยวข้อง นอกจากนี้ยังมีงานอื่น ๆ อีกมากมายที่ต้องทำ แต่จะไม่ลงรายละเอียด

  3. แผนการเพิ่มประสิทธิภาพการสืบค้น: ในขั้นตอนนี้ตัดสินใจถูกสร้างขึ้นเพื่อค้นหาวิธีการดำเนินการสืบค้น พบจำนวนวิธีที่สามารถดำเนินการสืบค้นและต้นทุนที่เกี่ยวข้องกับวิธีการดำเนินการค้นหาแต่ละวิธี เลือกแผนการที่ดีที่สุดสำหรับการดำเนินการค้นหา

  4. แคช: แผนที่ดีที่สุดที่เลือกในแผนการเพิ่มประสิทธิภาพการค้นหาจะถูกเก็บไว้ในแคชดังนั้นเมื่อใดก็ตามที่มีการสืบค้นเดียวกันในครั้งต่อไปก็ไม่จำเป็นต้องผ่านขั้นตอนที่ 1 ระยะที่ 2 และระยะที่ 3 อีก เมื่อมีการสืบค้นในครั้งต่อไประบบจะตรวจสอบโดยตรงใน Cache และหยิบขึ้นมาจากที่นั่นเพื่อดำเนินการ

  5. เฟสการดำเนินการ: ในระยะนี้แบบสอบถามที่ให้มาจะถูกดำเนินการและข้อมูลจะถูกส่งกลับไปยังผู้ใช้เป็นResultSetวัตถุ

พฤติกรรมของ PreparedStatement API ในขั้นตอนข้างต้น

  1. PreparedStatements ไม่ใช่เคียวรี SQL ที่สมบูรณ์และมีตัวยึดตำแหน่งซึ่งในขณะรันจะถูกแทนที่ด้วยข้อมูลที่ผู้ใช้ให้มาจริง

  2. เมื่อใดก็ตามที่ PreparedStatment ที่มีตัวยึดตำแหน่งถูกส่งผ่านไปยังเอ็นจิน SQL Server จะผ่านขั้นตอนด้านล่าง

    1. ขั้นตอนการแยกวิเคราะห์และ Normalization
    2. เฟสคอมไพล์
    3. แผนการเพิ่มประสิทธิภาพการสืบค้น
    4. แคช (การค้นหาที่รวบรวมด้วยตัวยึดตำแหน่งจะถูกเก็บไว้ใน Cache)

อัปเดตผู้ใช้ตั้งชื่อผู้ใช้ =? และรหัสผ่าน =? id = ที่ไหน

  1. ข้อความค้นหาข้างต้นจะได้รับการแยกวิเคราะห์รวบรวมด้วยตัวยึดตำแหน่งเป็นการปฏิบัติพิเศษปรับให้เหมาะสมและรับแคช แบบสอบถามในขั้นตอนนี้ได้รับการรวบรวมและแปลงในรูปแบบที่เครื่องเข้าใจได้แล้ว ดังนั้นเราจึงสามารถพูดได้ว่า Query ที่เก็บไว้ในแคชเป็น Pre-Compiled และต้องแทนที่ตัวยึดตำแหน่งด้วยข้อมูลที่ผู้ใช้ให้มาเท่านั้น

  2. ขณะนี้อยู่ในช่วงรันไทม์เมื่อมีข้อมูลที่ผู้ใช้ให้มาการสืบค้นที่รวบรวมไว้ล่วงหน้าจะถูกดึงขึ้นมาจากแคชและตัวยึดตำแหน่งจะถูกแทนที่ด้วยข้อมูลที่ผู้ใช้ให้มา

PrepareStatementWorking

(โปรดจำไว้ว่าหลังจากแทนที่ตัวยึดตำแหน่งด้วยข้อมูลผู้ใช้แบบสอบถามสุดท้ายจะไม่ถูกคอมไพล์ / ตีความอีกและเอ็นจิน 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


3
คำอธิบายที่ดี
— Dheeraj Joshi

4
คำตอบที่สมบูรณ์ที่สุดอย่างแท้จริงเกี่ยวกับวิธีการทำงาน
— jouell

มันเป็นประโยชน์มาก ขอบคุณสำหรับคำอธิบายโดยละเอียด
— Unknown

26

SQL ที่ใช้ใน PreparedStatement ถูกคอมไพล์ไว้ล่วงหน้าบนไดรเวอร์ จากจุดนั้นพารามิเตอร์จะถูกส่งไปยังไดรเวอร์เป็นค่าตัวอักษรและไม่ใช่ส่วนที่เรียกใช้งานได้ของ SQL ดังนั้นจึงไม่สามารถฉีด SQL โดยใช้พารามิเตอร์ได้ ผลข้างเคียงที่เป็นประโยชน์อีกประการหนึ่งของ PreparedStatements (precompilation + ส่งเฉพาะพารามิเตอร์) คือประสิทธิภาพที่ดีขึ้นเมื่อเรียกใช้คำสั่งหลายครั้งแม้จะมีค่าต่างกันสำหรับพารามิเตอร์ (สมมติว่าไดรเวอร์รองรับ PreparedStatements) เนื่องจากไดรเวอร์ไม่จำเป็นต้องทำการแยกวิเคราะห์และรวบรวม SQL เวลาที่พารามิเตอร์เปลี่ยนไป


ไม่จำเป็นต้องดำเนินการเช่นนั้นและฉันเชื่อว่ามักจะไม่เป็นเช่นนั้น
— Tom Hawtin - แท็กไลน์

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

3

ฉันเดาว่ามันจะเป็นสตริง แต่พารามิเตอร์อินพุตจะถูกส่งไปยังฐานข้อมูลและจะใช้การส่ง / การแปลงที่เหมาะสมก่อนสร้างคำสั่ง SQL จริง

เพื่อเป็นตัวอย่างอาจลองดูว่าแคสต์ / คอนเวอร์ชั่นใช้ได้หรือไม่
ถ้ามันใช้งานได้มันสามารถสร้างคำสั่งสุดท้ายจากมันได้

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

ลองใช้ตัวอย่างคำสั่ง SQL ที่ยอมรับพารามิเตอร์ตัวเลข
ตอนนี้ให้ลองส่งตัวแปรสตริง (พร้อมเนื้อหาตัวเลขที่ยอมรับเป็นพารามิเตอร์ตัวเลข) มันทำให้เกิดข้อผิดพลาดหรือไม่?

ตอนนี้ให้ลองส่งตัวแปรสตริง (ด้วยเนื้อหาที่ไม่สามารถยอมรับได้เป็นพารามิเตอร์ตัวเลข) ดูว่าเกิดอะไรขึ้น?


3

งบที่เตรียมไว้มีความปลอดภัยมากขึ้น มันจะแปลงพารามิเตอร์เป็นชนิดที่ระบุ

ตัวอย่างเช่นstmt.setString(1, user);จะแปลงuserพารามิเตอร์เป็นสตริง

สมมติว่าพารามิเตอร์มีสตริง SQL ที่มีคำสั่งปฏิบัติการ : การใช้ที่เตรียมไว้จะไม่อนุญาตให้ทำเช่นนั้น

มันจะเพิ่ม metacharacter (aka auto conversion) เข้าไป

ทำให้ปลอดภัยมากขึ้น


2

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/


1
ถ้าฉันจำไม่ผิดส่วนCAST(‘Robert’);จากCAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))จะแตกแล้วมันจะวางตารางต่อไปหากเป็นเช่นนั้น มันหยุดการฉีดดังนั้นฉันเชื่อว่าตัวอย่างยังไม่สมบูรณ์เพียงพอที่จะอธิบายสถานการณ์
— HéctorÁlvarez

1

PreparedStatement:

1) การคอมไพล์ล่วงหน้าและการแคชฝั่ง DB ของคำสั่ง SQL นำไปสู่การดำเนินการโดยรวมที่เร็วขึ้นและความสามารถในการนำคำสั่ง SQL เดิมกลับมาใช้ใหม่เป็นชุด

2) การป้องกันการโจมตีด้วยการฉีด SQL โดยอัตโนมัติโดยการหลีกเลี่ยงเครื่องหมายคำพูดและอักขระพิเศษอื่น ๆ ในตัว โปรดทราบว่าสิ่งนี้ต้องการให้คุณใช้เมธอด PreparedStatement setXxx () เพื่อตั้งค่า


1

ตามที่อธิบายไว้ในโพสต์นี้ไฟล์PreparedStatementเพียงอย่างเดียวไม่ช่วยให้คุณถ้าคุณยังคงเชื่อมโยง Strings

ตัวอย่างเช่นผู้โจมตีโกงหนึ่งคนยังสามารถทำสิ่งต่อไปนี้ได้:

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

ไม่เพียง แต่ SQL เท่านั้น แต่แม้แต่ JPQL หรือ HQL ก็สามารถถูกบุกรุกได้หากคุณไม่ได้ใช้พารามิเตอร์การโยง

บรรทัดล่างสุดคุณไม่ควรใช้การต่อสายอักขระเมื่อสร้างคำสั่ง SQL ใช้ API เฉพาะเพื่อจุดประสงค์นั้น:


1
ขอขอบคุณที่ชี้ให้เห็นถึงความสำคัญของการใช้การเชื่อมโยงพารามิเตอร์แทนที่จะเป็น PreparedStatement เพียงอย่างเดียว อย่างไรก็ตามคำตอบของคุณดูเหมือนจะบอกเป็นนัยว่าการใช้ API เฉพาะเป็นสิ่งจำเป็นเพื่อป้องกันการแทรก SQL เนื่องจากไม่เป็นเช่นนั้นและการใช้ PreparedStatement กับการเชื่อมโยงพารามิเตอร์ก็ใช้ได้เช่นกันคุณต้องการจัดรูปแบบใหม่หรือไม่?
— Wild Pottok

-3

ใน Prepared Statements ผู้ใช้ถูกบังคับให้ป้อนข้อมูลเป็นพารามิเตอร์ หากผู้ใช้ป้อนคำสั่งที่มีช่องโหว่เช่น DROP TABLE หรือ SELECT * FROM USERS ข้อมูลจะไม่ได้รับผลกระทบเนื่องจากจะถือว่าเป็นพารามิเตอร์ของคำสั่ง SQL


คำตอบเดียวกับคำตอบที่เลือกซึ่งมีความแม่นยำน้อยกว่า
— Julien Maret
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.