ขั้นตอนการจัดเก็บป้องกันการฉีด SQL หรือไม่


83

เป็นความจริงหรือไม่ที่ขั้นตอนการจัดเก็บป้องกันการโจมตี SQL injection กับฐานข้อมูล PostgreSQL ฉันทำวิจัยเล็กน้อยและพบว่า SQL Server, Oracle และ MySQL ไม่ปลอดภัยต่อการฉีด SQL แม้ว่าเราจะใช้ขั้นตอนการจัดเก็บเท่านั้น อย่างไรก็ตามปัญหานี้ไม่มีอยู่ใน PostgreSQL

การใช้โพรซีเดอร์ที่เก็บไว้ในแกน PostgreSQL ป้องกันการโจมตีจากการฉีด SQL หรือเป็นอย่างอื่นหรือไม่? หรือ PostgreSQL มีความอ่อนไหวต่อการฉีด SQL แม้ว่าเราจะใช้ขั้นตอนการจัดเก็บเท่านั้น ถ้าเป็นเช่นนั้นโปรดแสดงตัวอย่าง (เช่นหนังสือไซต์กระดาษ ฯลฯ )


4
แปลกคำตอบด้านบนที่นี่ส่วนใหญ่จะ OT รับมือกับ SQL Server ในขณะที่ยังเป็นคำถามที่เกี่ยวกับPostgres นี่คือคำตอบที่เกี่ยวข้องสำหรับ Postgres: dba.stackexchange.com/questions/49699/... มีคนอื่นอีกสองสามคนลองค้นหา: dba.stackexchange.com/ …
Erwin Brandstetter

@ErwinBrandstetter คำถามเดิมไม่ได้ติดแท็ก (โดย OP) ด้วย postgres และ - และยัง - กล่าวถึง DBMS อื่น ๆ อีกหลาย ฉันเดาว่าเป็นเหตุผลของคำตอบต่าง ๆ ที่มุ่งเน้นไปที่ DBMS อื่น ๆ ฉันขอแนะนำให้คุณเพิ่มอีกหนึ่งความสำคัญกับ Postgres
ypercubeᵀᴹ

@ ypercubeᵀᴹ: ฉันจะเพิ่มคำตอบที่นี่เมื่อฉันหาเวลา ในระหว่างนี้ฉันได้อัปเดตdba.stackexchange.com/questions/49699/…ให้ชัดเจนและครอบคลุมยิ่งขึ้น
Erwin Brandstetter

คำตอบ:


71

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

รหัสเซิร์ฟเวอร์ sql นี้:

CREATE PROCEDURE [dbo].[sp_colunmName2]   
    @columnName as nvarchar(30),
    @type as nvarchar(30), 
    @searchText as nvarchar(30)           
AS
BEGIN
    DECLARE @SQLStatement NVARCHAR(4000)
    BEGIN
        SELECT @SQLStatement = 'select * from Stations where ' 
            + @columnName + ' ' + @type + ' ' + '''' + @searchText + '''' 
        EXEC(@SQLStatement)
    END      
END
GO

ประมาณเทียบเท่ากับ postgres:

CREATE or replace FUNCTION public.sp_colunmName2 (
    columnName  varchar(30),
    type varchar(30), 
    searchText  varchar(30) ) RETURNS SETOF stations LANGUAGE plpgsql            
AS
$$
DECLARE SQLStatement VARCHAR(4000);
BEGIN
    SQLStatement = 'select * from Stations where ' 
            || columnName || ' ' || type || ' ' || ''''|| searchText || '''';
    RETURN QUERY EXECUTE  SQLStatement;
END
$$;

แนวคิดของนักพัฒนาคือการสร้างขั้นตอนการค้นหาที่หลากหลาย แต่ผลลัพธ์คือประโยค WHERE สามารถมีทุกสิ่งที่ผู้ใช้ต้องการอนุญาตให้เยี่ยมชมจากBobby Tablesเล็กน้อย

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


46

การโจมตี SQL-Injection เป็นสิ่งที่ข้อมูลที่ไม่น่าเชื่อถือนั้นถูกผนวกเข้ากับการสืบค้นโดยตรงช่วยให้ผู้ใช้สามารถรันโค้ดได้อย่างมีประสิทธิภาพตามที่แสดงในการ์ตูน XKCD ที่เป็นที่ยอมรับ

ดังนั้นเราจึงได้รับสถานการณ์:

userInput = getFromHTML # "Robert ') วางตารางนักเรียน - -"

แบบสอบถาม = "เลือก * จากนักเรียนที่ studentName =" + userInput

โดยทั่วไปแล้วกระบวนงานที่เก็บไว้จะป้องกันการโจมตี SQL injection ได้ดีเนื่องจากพารามิเตอร์ขาเข้าไม่เคยถูกวิเคราะห์คำ

ในโพรซีเดอร์ที่เก็บไว้ในฐานข้อมูลส่วนใหญ่ (และโปรแกรมอย่าลืมว่าคิวรีที่คอมไพล์แล้วนับเป็นโพรซีเดอร์ที่เก็บไว้) จะมีลักษณะดังนี้:

 

สร้างรายการ procdure foo (
เลือก * จากนักเรียนที่ studentName =: 1
);

จากนั้นเมื่อโปรแกรมต้องการการเข้าถึงโปรแกรมจะเรียกfoo(userInput)และเรียกผลลัพธ์อย่างมีความสุข

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

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ SQL-Injection:


29

ใช่ในระดับหนึ่ง
กระบวนงานที่เก็บไว้เพียงอย่างเดียวจะไม่ป้องกันการฉีด SQL

ให้ฉันพูดเกี่ยวกับ SQL Injection จากOWASP ก่อน

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

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

Jeff Attwood อธิบายผลที่ตามมาของการเชื่อม SQL เข้าด้วยกันใน " ให้พารามิเตอร์ของ SQL หรือทำให้ฉันตาย "

ต่อไปนี้เป็นการ์ตูนที่น่าสนใจซึ่งอยู่ในใจของฉันเมื่อใดก็ตามที่ฉันได้ยิน SQL Injection ข้อความแสดงแทน ฉันคิดว่าคุณมีประเด็น :-)

ลองดูที่SQL Injection Prevention Cheat Sheet , วิธีการป้องกันถูกอธิบายอย่างเรียบร้อย ...


12

การต่อสตริงเป็นสาเหตุของ SQL Injection สิ่งนี้หลีกเลี่ยงได้โดยใช้การตั้งค่าพารามิเตอร์

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

ดังนั้นรหัสของคุณข้างต้นเกิดจากการรวมกันของสตริงเหล่านี้

  • exec sp_GetUser '
  • x' AND 1=(SELECT COUNT(*) FROM Client); --
  • ' , '
  • monkey
  • '

สิ่งนี้ทำให้ไวยากรณ์ไม่ถูกต้องโชคดี

Parametrisingมันจะให้

exec sp_GetUser 'x'' AND 1=(SELECT COUNT(*) FROM Client); --' , 'monkey'

ซึ่งหมายความว่า

  • @UserName = x' AND 1=(SELECT COUNT(*) FROM Client); --
  • @Password = monkey

ตอนนี้ในรหัสข้างต้นคุณจะไม่ได้แถวเพราะฉันถือว่าคุณไม่มีผู้ใช้ x' AND 1=(SELECT COUNT(*) FROM Client); --

หาก proc ที่เก็บไว้มีลักษณะเช่นนี้ (โดยใช้การเชื่อมต่อSQL แบบไดนามิก ) ดังนั้นการเรียก proc ที่เก็บไว้ของคุณจะยังคงอนุญาตให้ SQL Injection

...
SET @sql = 'SELECT userName from users where userName = ''' + 
               @UserName + 
               ''' and userPass = ''' +
               @Password +
               ''''
EXEC (@sql)
....

ดังนั้นดังที่แสดงให้เห็นว่าการต่อสตริงเป็นศัตรูหลักสำหรับการฉีด SQL

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

คุณสามารถดู Stack Overflow สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการตั้งค่าพารามิเตอร์


10

"โจมตีฉีด SQL เกิดขึ้นเมื่อผู้ใช้ป้อนข้อมูลจะถูกเข้ารหัสไม่ถูกต้อง. โดยปกติแล้วผู้ใช้ป้อนข้อมูลบางส่วนของผู้ใช้จะส่งแบบสอบถามของเธอค่าเช่นใน$_GET, $_POST, $_COOKIE, $_REQUESTหรือ$_SERVERอาร์เรย์. แต่ท่านผู้ใช้ยังสามารถมาจากความหลากหลายของอื่น ๆ แหล่งที่มาเช่นซ็อกเก็ต, เว็บไซต์ระยะไกล, ไฟล์, ฯลฯ ดังนั้นคุณควรปฏิบัติต่อทุกสิ่งทุกอย่างยกเว้นค่าคงที่ (เช่น'foobar') เป็นอินพุตของผู้ใช้ "

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



จาก YouTube


จากวิกิพีเดีย


จาก OWASP


จากคู่มือ PHP


จาก Microsoft และ Oracle


กองล้นมากเกินไป


เครื่องสแกนการฉีด SQL


2

กระบวนงานที่เก็บไว้ไม่ได้ป้องกันการฉีด SQL อย่างน่าอัศจรรย์ แต่มันจะป้องกันได้ง่ายกว่ามาก สิ่งที่คุณต้องทำคือสิ่งต่อไปนี้ (ตัวอย่าง Postgres):

CREATE OR REPLACE FUNCTION my_func (
  IN in_user_id INT 
)
[snip]
  SELECT user_id, name, address FROM my_table WHERE user_id = in_user_id; --BAM! SQL INJECTION IMMUNE!!
[snip]

แค่นั้นแหละ! ปัญหาเกิดขึ้นเมื่อสร้างแบบสอบถามผ่านการต่อสตริง (เช่นไดนามิก SQL) และแม้แต่ในกรณีเหล่านั้นคุณอาจสามารถผูก! (ขึ้นอยู่กับฐานข้อมูล)

วิธีหลีกเลี่ยงการฉีด SQL ในคิวรีแบบไดนามิกของคุณ:

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

ขั้นตอนที่ 2) วิจัยวิธีที่เหมาะสมในการตั้งค่าตัวแปรสำหรับ RDBMS เฉพาะของคุณ ตัวอย่างเช่น Oracle ช่วยให้คุณทำสิ่งต่อไปนี้ (อ้างอิงจากเอกสารของพวกเขา):

sql_stmt := 'UPDATE employees SET salary = salary + :1 WHERE ' 
           || v_column || ' = :2';
EXECUTE IMMEDIATE sql_stmt USING amount, column_value; --INJECTION IMMUNE!!

ที่นี่คุณยังไม่ได้เชื่อมต่ออินพุต คุณผูกมัดอย่างปลอดภัย! ไชโย!

หากฐานข้อมูลของคุณไม่รองรับสิ่งที่กล่าวมาข้างต้น (หวังว่าจะไม่มีสิ่งใดในพวกเขาที่ยังไม่ดี แต่ฉันจะไม่แปลกใจ) - หรือถ้าคุณยังต้องเชื่อมต่อข้อมูลของคุณ (เช่นในกรณี "แบบสอบถาม" รายงาน ฉันพูดตามข้างบน) แล้วคุณต้องใช้ฟังก์ชั่นการหลบหนีที่เหมาะสม อย่าเขียนเอง ตัวอย่างเช่น postgres ให้ฟังก์ชัน quote_literal () ดังนั้นคุณจะทำงาน:

sql_stmt := 'SELECT salary FROM employees WHERE name = ' || quote_literal(in_name);

วิธีนี้หาก in_name เป็นสิ่งที่คดเคี้ยวเช่น '[snip] หรือ 1 = 1' (ส่วน "หรือ 1 = 1" หมายถึงเลือกแถวทั้งหมดช่วยให้ผู้ใช้เห็นเงินเดือนที่เขาไม่ควร!) แล้ว quote_literal ช่วยลดก้นของคุณด้วย ทำให้สตริงผลลัพธ์:

SELECT salary FROM employees WHERE name = '[snip] or 1=1'

จะไม่พบผลลัพธ์ใด ๆ (เว้นแต่คุณจะมีพนักงานบางคนที่มีชื่อแปลก ๆ )

นั่นคือส่วนสำคัญของมัน! ตอนนี้ฉันจะปล่อยให้คุณมีลิงค์ไปยังโพสต์คลาสสิกโดย Oracle guru Tom Kyte ในเรื่องของ SQL Injection เพื่อขับเคลื่อนจุดกลับบ้าน: Linky


อย่าลืมพูดถึงquote_ident()แต่โดยทั่วไปวิธีที่ง่ายที่สุดในการเขียนไดนามิก SQL ที่ป้องกันการฉีดคือการใช้format()และใช้ตัวยึด%Iสำหรับตัวระบุและ%Lตัวอักษร วิธีการที่ SQL คืออ่านได้มากขึ้นกว่ารุ่นเทียบเท่าการใช้||และquote_....()ฟังก์ชั่น
a_horse_with_no_name
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.