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.