ทำไมไม่เพียงแค่สร้างแบบสอบถามที่ไม่ใช่พารามิเตอร์ส่งคืนข้อผิดพลาด?


22

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

ดูเหมือนกับฉันว่าวิธีที่ชัดเจนในการแก้ไขปัญหานี้ก็คือการปิดตัวเลือกที่ชัดเจน (แต่ผิด): แก้ไขเอ็นจิ้นฐานข้อมูลเพื่อให้แบบสอบถามใด ๆ ที่ได้รับซึ่งใช้ค่าตายตัวในส่วนคำสั่ง WHERE แทนที่จะเป็นพารามิเตอร์ ข้อความแสดงข้อผิดพลาดแจ้งให้คุณใช้พารามิเตอร์แทน สิ่งนี้จะต้องมีตัวเลือกในการยกเลิกเพื่อให้สิ่งต่าง ๆ เช่นแบบสอบถาม ad-hoc จากเครื่องมือการดูแลระบบจะยังคงทำงานได้อย่างง่ายดาย แต่ควรเปิดใช้งานตามค่าเริ่มต้น

การมีสิ่งนี้จะทำให้การฉีด SQL เย็นลงเกือบข้ามคืน แต่เท่าที่ฉันรู้ไม่มี RDBMS ทำสิ่งนี้ได้จริง มีเหตุผลที่ดีทำไมไม่


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'จะมีทั้งค่าตายตัวและกำหนดค่าพารามิเตอร์ในแบบสอบถามเดียว - ลองจับมัน! ฉันคิดว่ามีกรณีการใช้งานที่ถูกต้องสำหรับข้อความค้นหาแบบผสมเช่นนั้น
amon

6
วิธีเลือกระเบียนตั้งแต่วันนี้SELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee

10
@MasonWheeler ขอโทษฉันหมายถึง "พยายามที่จะอนุญาตให้" โปรดทราบว่ามันเป็นพารามิเตอร์ที่สมบูรณ์แบบและไม่ประสบจากการฉีด SQL อย่างไรก็ตามไดรเวอร์ฐานข้อมูลไม่สามารถบอกได้ว่าตัวอักษร"bad"นั้นแท้จริงตามตัวอักษรจริงหรือเป็นผลมาจากการต่อสตริง โซลูชันทั้งสองที่ฉันเห็นมีทั้งการกำจัด SQL และ DSL ที่ฝังตัวด้วยสตริงอื่น ๆ (ใช่ได้โปรด) หรือโปรโมตภาษาที่การต่อสตริงนั้นน่ารำคาญกว่าการใช้การค้นหาแบบใช้พารามิเตอร์ (umm, no)
amon

4
และ RDBMS จะตรวจพบได้อย่างไรว่าจะทำเช่นนี้? มันจะทำให้การเข้าถึง RDBMS ข้ามคืนไม่สามารถทำได้โดยใช้พรอมต์ SQL แบบโต้ตอบ ... คุณจะไม่สามารถป้อนคำสั่ง DDL หรือ DML ได้อีกต่อไปโดยใช้เครื่องมือใด ๆ เลย
jwenting

8
ในแง่ที่คุณสามารถทำได้: อย่าสร้างแบบสอบถาม SQL ที่รันไทม์เลยแทนที่จะใช้ ORM หรือเลเยอร์ abstraction อื่น ๆ แทนเพื่อหลีกเลี่ยงการสร้างแบบสอบถาม SQL ORM ไม่มีคุณสมบัติที่คุณต้องการ? จากนั้น SQL เป็นภาษาที่มีไว้สำหรับผู้ที่ต้องการเขียน SQL ซึ่งเป็นสาเหตุที่ทำให้พวกเขาเขียน SQL ปัญหาพื้นฐานคือการสร้างรหัสแบบไดนามิกนั้นยากกว่าที่จะมอง แต่คนต้องการที่จะทำมันต่อไปและจะไม่พอใจกับผลิตภัณฑ์ที่จะไม่ปล่อยให้พวกเขา
Steve Jessop

คำตอบ:


45

มีหลายกรณีที่การใช้ตัวอักษรเป็นวิธีการที่ถูกต้อง

จากมุมมองด้านประสิทธิภาพมีหลายครั้งที่คุณต้องการตัวอักษรในแบบสอบถามของคุณ ลองนึกภาพฉันมีตัวติดตามบั๊กซึ่งเมื่อมันใหญ่พอที่จะกังวลเกี่ยวกับประสิทธิภาพฉันคาดว่า 70% ของข้อบกพร่องในระบบจะเป็น "ปิด" 20% จะเป็น "เปิด" 5% จะเป็น "ใช้งาน" และ 5 % จะอยู่ในสถานะอื่น ฉันอาจต้องการให้มีการสืบค้นที่ส่งคืนบั๊กที่ใช้งานอยู่ทั้งหมด

SELECT *
  FROM bug
 WHERE status = 'active'

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

จากมุมมองความซับซ้อนของรหัสมีหลายครั้งที่มันเหมาะสมที่จะมีตัวอักษรในคำสั่ง SQL ตัวอย่างเช่นหากคุณมีzip_codeคอลัมน์ที่มีรหัสไปรษณีย์ 5 ตัวอักษรและบางครั้งก็มีตัวเลข 4 หลักเพิ่มเติมมันเหมาะสมที่จะทำสิ่งที่ชอบ

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

แทนที่จะส่งผ่านพารามิเตอร์ที่แยกต่างหาก 4 ตัวสำหรับค่าตัวเลข สิ่งเหล่านี้ไม่ใช่สิ่งที่จะเปลี่ยนแปลงดังนั้นการทำให้พวกเขาผูกตัวแปรทำหน้าที่เฉพาะเพื่อทำให้รหัสอาจยากต่อการอ่านและเพื่อสร้างศักยภาพที่ใครบางคนจะผูกพารามิเตอร์ในลำดับที่ไม่ถูกต้องและจบลงด้วยข้อผิดพลาด


12

การฉีด SQL เกิดขึ้นเมื่อแบบสอบถามถูกสร้างขึ้นโดยการเชื่อมข้อความจากแหล่งข้อมูลที่ไม่น่าเชื่อถือและไม่น่าเชื่อถือกับส่วนอื่น ๆ ของแบบสอบถาม ในขณะที่สิ่งต่าง ๆ ส่วนใหญ่มักจะเกิดขึ้นกับตัวอักษรสตริงที่จะไม่เป็นวิธีเดียวที่มันอาจเกิดขึ้น แบบสอบถามสำหรับค่าตัวเลขอาจใช้สตริงที่ผู้ใช้ป้อน (นั่นคือควรจะมีเฉพาะตัวเลข) และต่อเชื่อมกับเนื้อหาอื่น ๆ เพื่อสร้างแบบสอบถามโดยไม่ต้องมีเครื่องหมายอัญประกาศที่เกี่ยวข้องกับสตริงตัวอักษร รหัสที่ไว้วางใจมากเกินไปของการตรวจสอบฝั่งไคลเอ็นต์อาจมีสิ่งต่าง ๆ เช่นชื่อเขตข้อมูลมาจากสตริงการสืบค้น HTML ไม่มีวิธีการดูรหัสแบบสอบถาม SQL สามารถดูวิธีการประกอบ

สิ่งที่สำคัญไม่ใช่ว่าคำสั่ง SQL จะมีตัวอักษรสตริงหรือไม่ แต่สตริงนั้นมีลำดับอักขระใด ๆจากแหล่งที่ไม่น่าเชื่อถือและการตรวจสอบความถูกต้องสำหรับสิ่งนั้นจะได้รับการจัดการที่ดีที่สุดในห้องสมุดที่สร้างแบบสอบถาม โดยทั่วไปแล้วจะไม่มีวิธีในการเขียนโค้ด C # ที่จะอนุญาตให้มีตัวอักษรสตริง แต่จะไม่อนุญาตให้มีการแสดงออกของสตริงชนิดอื่น แต่มีกฎการเข้ารหัสที่ต้องการให้สร้างแบบสอบถามโดยใช้คลาสการสร้างคิวรีมากกว่า การต่อสตริงและทุกคนที่ผ่านสตริงที่ไม่ใช่ตัวอักษรไปยังตัวสร้างแบบสอบถามจะต้องพิสูจน์การกระทำดังกล่าว


1
ในการประมาณค่า "มันเป็นตัวอักษร" คุณสามารถตรวจสอบว่าสตริงถูก interned หรือไม่
CodesInChaos

1
@CodesInChaos: True และการทดสอบดังกล่าวอาจถูกต้องเพียงพอสำหรับวัตถุประสงค์นี้โดยที่ทุกคนที่มีเหตุผลในการสร้างสตริงที่ runtime ใช้วิธีการที่ยอมรับสตริงที่ไม่ใช่ตัวอักษรแทนที่จะใช้สตริงที่สร้างขึ้นจาก runtime และการใช้ (ให้วิธีการที่ไม่ใช่ตัวอักษรชื่ออื่นจะทำให้เป็นเรื่องง่ายสำหรับผู้ตรวจสอบรหัสในการตรวจสอบการใช้งานทั้งหมดของมัน)
supercat

โปรดทราบว่าในขณะที่ไม่มีวิธีการทำเช่นนี้ใน C # ภาษาอื่น ๆ บางอย่างมีสิ่งอำนวยความสะดวกที่ทำให้เป็นไปได้ (เช่นโมดูลสตริงที่เสียของ Perl)
จูลส์

โดยสังเขปนี่เป็นปัญหาของลูกค้าไม่ใช่ปัญหาของเซิร์ฟเวอร์
Blrfl

7
SELECT count(ID)
FROM posts
WHERE deleted = false

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

ตอนนี้คุณสามารถพูดได้ว่าคุณจะเพิ่มข้อยกเว้นสำหรับ enums แต่เพียงแค่เปิดช่องอีกครั้ง (แม้ว่าจะเล็กกว่า) ไม่พูดถึงคนแรกต้องได้รับการศึกษาที่จะไม่ใช้varcharsสำหรับผู้ที่

ปัญหาที่แท้จริงของการฉีดคือการสร้างสตริงแบบสอบถาม วิธีการแก้ปัญหานั้นเป็นกลไกขั้นตอนการจัดเก็บและบังคับใช้หรือรายการที่อนุญาตของแบบสอบถามที่ได้รับอนุญาต


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

5
ฉันเห็นการฉีด SQL ผ่านกระบวนงานที่เก็บไว้ในที่ทำงาน มันกลับกลายเป็นข้อบังคับขั้นตอนการจัดเก็บสำหรับทุกสิ่งที่ไม่ดี มีอยู่เสมอที่ 0.5% ที่เป็นคิวรีแบบไดนามิกที่แท้จริง (คุณไม่สามารถกำหนดขอบเขตทั้งหมดในกรณีที่ประโยครวมเข้าด้วยกันเป็นตาราง)
Joshua

ในตัวอย่างในคำตอบนี้คุณสามารถแทนที่deleted = falseด้วยNOT deletedซึ่งหลีกเลี่ยงตัวอักษร แต่ประเด็นก็ใช้ได้โดยทั่วไป
psmears

5

TL; DR : คุณต้อง จำกัดตัวอักษรทั้งหมดไม่ใช่เฉพาะในWHEREประโยค สำหรับเหตุผลที่พวกเขาทำไม่ได้มันอนุญาตให้ฐานข้อมูลยังคงถูกแยกออกจากระบบอื่น ๆ

ประการแรกหลักฐานของคุณมีข้อบกพร่อง คุณต้องการ จำกัด เฉพาะส่วนWHEREคำสั่งเท่านั้นแต่นั่นไม่ได้เป็นเพียงการป้อนข้อมูลของผู้ใช้เท่านั้น ตัวอย่างเช่น,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

นี่คือความเสี่ยงที่เท่าเทียมกันในการฉีด SQL:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

ดังนั้นคุณไม่สามารถ จำกัด ตัวอักษรในWHEREประโยคได้ คุณต้อง จำกัดตัวอักษรทั้งหมด

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

เป็นตัวอย่างทั่วไปตัวอักษรมักใช้ในประชากรของตาราง list-of-value / look-up:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

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

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

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

พยายามทำเช่นนั้นจะเป็นฝันร้ายของการบำรุงรักษาโดยเฉพาะอย่างยิ่งเนื่องจากไวยากรณ์ที่ถูกต้องมักจะเปลี่ยนแปลงระหว่างการออกรุ่นใหญ่ของฐานข้อมูล

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