งบที่เตรียมไว้สามารถป้องกันการโจมตีจากการฉีด SQL ได้อย่างไร


172

วิธีการทำงบเตรียมช่วยเราป้องกันการฉีด SQLการโจมตี?

Wikipedia พูดว่า:

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

ฉันไม่เห็นเหตุผลที่ดีมาก อะไรจะเป็นคำอธิบายที่ง่ายในภาษาอังกฤษง่าย ๆ และตัวอย่างบางส่วน?

คำตอบ:


290

ความคิดคือง่ายมาก - แบบสอบถามและข้อมูลที่ถูกส่งไปยังเซิร์ฟเวอร์ฐานข้อมูลแยกต่างหาก
นั่นคือทั้งหมดที่

รากของปัญหาการฉีด SQL อยู่ในการผสมของรหัสและข้อมูล

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

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

จะสร้างแบบสอบถามปกติ

SELECT * FROM users where id=1

ในขณะที่รหัสนี้

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

จะสร้างลำดับที่เป็นอันตราย

SELECT * FROM users where id=1; DROP TABLE users;

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

แม้ว่าเราจะไม่ได้ทำการเปลี่ยนแปลงโปรแกรม แต่อย่างใด แต่ก็ยังคงไม่เปลี่ยนแปลง
นั่นคือประเด็น

เรากำลังส่งโปรแกรมไปยังเซิร์ฟเวอร์ก่อน

$db->prepare("SELECT * FROM users where id=?");

โดยที่ข้อมูลถูกแทนที่ด้วยตัวแปรบางตัวที่เรียกว่าพารามิเตอร์หรือตัวยึดตำแหน่ง

โปรดทราบว่าข้อความค้นหาเดียวกันถูกส่งไปยังเซิร์ฟเวอร์โดยไม่มีข้อมูลใด ๆ ! จากนั้นเราจะส่งข้อมูลด้วยการร้องขอครั้งที่สองโดยแยกออกจากแบบสอบถามเป็นหลัก:

$db->execute($data);

ดังนั้นจึงไม่สามารถแก้ไขโปรแกรมของเราและทำอันตรายใด ๆ
ค่อนข้างง่าย - ใช่มั้ย

สิ่งเดียวที่ฉันต้องเพิ่มที่ละไว้เสมอในคู่มือทั้งหมด:

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


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

3
@ zaq178miami: "PDO เลียนแบบคำสั่งที่เตรียมไว้สำหรับไดรเวอร์ที่ไม่รองรับคุณสมบัติ" เท่านั้น - ไม่เป็นความจริง MySQL รองรับงบที่เตรียมไว้มาระยะหนึ่งแล้ว ไดรเวอร์ PDO มีเช่นกัน แต่ยังแบบสอบถาม MySQL ยังเตรียมโดย PDO โดยค่าเริ่มต้นครั้งสุดท้ายที่ฉันตรวจสอบ
cHao

9
อะไรคือสิ่งที่แตกต่างกัน$spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";เมื่อเทียบกับ: $db->prepare("SELECT * FROM users where id=?");-> ->$data = "1; DROP TABLE users;" $db->execute($data);พวกเขาจะไม่ทำสิ่งเดียวกันหรือไม่
Juha Untinen

14
@Juha Untinen ข้อมูลสามารถเป็นอะไรก็ได้ มันจะไม่แยกวิเคราะห์ข้อมูล นั่นคือ DATA ไม่ใช่คำสั่ง ดังนั้นแม้ว่าข้อมูล $ จะมีคำสั่ง sql แต่จะไม่ถูกดำเนินการ นอกจากนี้หาก id เป็นตัวเลขเนื้อหาสตริงจะสร้างรายงานหรือค่าศูนย์
Soley

21

นี่คือ SQL สำหรับการตั้งค่าตัวอย่าง:

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

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

import java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

การดำเนินการนี้กรณีแรกคือการใช้งานปกติและกรณีที่สองเป็นการฉีดที่เป็นอันตราย

c:\temp>java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

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

นี่คือตัวอย่างของการผูกเพื่อหลีกเลี่ยงการฉีดชนิดนี้:

import java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

การเรียกใช้สิ่งนี้ด้วยอินพุตเดียวกันกับตัวอย่างก่อนหน้านี้แสดงให้เห็นว่ารหัสที่เป็นอันตรายไม่ทำงานเพราะไม่มีประเภทการชำระเงินที่ตรงกับสตริงนั้น:

c:\temp>java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?

การใช้คำสั่งที่เตรียมไว้จากโปรแกรมที่เชื่อมต่อกับฐานข้อมูลมีผลเช่นเดียวกับการใช้คำสั่งที่เตรียมไว้ซึ่งเป็นส่วนหนึ่งของ db หรือไม่? ตัวอย่างเช่น Postgres มีคำสั่งที่เตรียมไว้แล้วและจะใช้เพื่อป้องกันการฉีด SQL หรือไม่ postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas

@Celeritas ฉันไม่มีคำตอบที่ชัดเจนสำหรับ Postgresql เมื่อมองไปที่เอกสารมันจะปรากฏผลเหมือนกัน PREPAREสร้างคำสั่งที่มีชื่อคงที่ที่แยกวิเคราะห์แล้ว (เช่นคำสั่งจะไม่เปลี่ยนแปลงอีกต่อไปโดยไม่คำนึงถึงอินพุต) ในขณะที่EXECUTEจะเรียกใช้คำสั่งที่มีชื่อผูกพารามิเตอร์ เนื่องจากPREPAREมีระยะเวลาเซสชันเท่านั้นดูเหมือนว่ามีความหมายสำหรับเหตุผลด้านประสิทธิภาพไม่ใช่เพื่อป้องกันการฉีดผ่านสคริปต์ psql สำหรับการเข้าถึง psql สามารถให้สิทธิ์กับโพรซีเดอร์ที่เก็บและผูกพารามิเตอร์ภายใน procs
เกล็น

@Celeritas ฉันลองรหัสข้างต้นโดยใช้ PostgreSQL 11.1 ใน x86_64 และตัวอย่าง SQLi ข้างต้นทำงาน
กฤษณะ Pandey

15

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

นี่เป็นเพราะคำสั่งที่เตรียมไว้ "เตรียม" แบบสอบถาม SQL ก่อนเพื่อค้นหาแผนแบบสอบถามที่มีประสิทธิภาพและส่งค่าจริงที่สันนิษฐานว่ามาจากฟอร์มในภายหลัง - ในเวลานั้นแบบสอบถามจะดำเนินการจริง

ข้อมูลเพิ่มเติมที่นี่:

งบเตรียมและฉีด SQL


6

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

วิธีการไร้เดียงสา

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

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"

ตัวอย่างเช่นการป้อนข้อมูลผู้ใช้ที่เป็นอันตรายสามารถนำไปสู่SQLStringการเท่ากับ"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

เนื่องจากผู้ใช้ที่เป็นอันตรายSQLStringมี 2 ​​ข้อความซึ่งหนึ่งใน"DROP TABLE CUSTOMERS"นั้นจะทำให้เกิดความเสียหาย

งบเตรียม

ในกรณีนี้เนื่องจากการแยกของแบบสอบถามและข้อมูลที่ผู้ใช้ป้อนจะไม่ถือว่าเป็นคำสั่ง SQL จึงจะไม่ดำเนินการ ด้วยเหตุผลนี้เองที่โค้ด SQL ที่เป็นอันตรายใด ๆ ที่ถูกฉีดเข้ามาจะไม่ก่อให้เกิดอันตรายใด ๆ ดังนั้น"DROP TABLE CUSTOMERS"จะไม่ถูกดำเนินการในกรณีข้างต้น

โดยสรุปคำสั่งที่เตรียมไว้โค้ดอันตรายที่แนะนำผ่านการป้อนข้อมูลของผู้ใช้จะไม่ถูกดำเนินการ!


จริงๆ? คำตอบที่ยอมรับไม่ได้บอกอย่างนั้นเหรอ?
สามัญสำนึกของคุณ

@ สามัญสำนึกของคุณคำตอบที่ได้รับการตอบรับนั้นเต็มไปด้วยข้อมูลที่มีค่ามากมาย แต่มันทำให้ฉันสงสัยว่ารายละเอียดการใช้งานของการแยกข้อมูล & ข้อความค้นหานั้นเป็นอย่างไร ในขณะที่มุ่งเน้นไปที่จุดที่ข้อมูลที่เป็นอันตราย (ถ้ามี) จะไม่ถูกประหารที่ตอกตะปูบนหัว
N.Vegeta

และ "รายละเอียดการใช้งาน" ใดที่ให้ไว้ในคำตอบของคุณซึ่งไม่ได้นำเสนอ
สามัญสำนึกของคุณ

หากคุณพยายามที่จะดูว่าฉันมาจากไหนคุณจะรู้ว่าประเด็นของฉันมีดังนี้: ความปรารถนาสั้น ๆ ที่จะเห็นรายละเอียดของการติดตั้งนั้นเกิดจากความจำเป็นที่จะต้องเข้าใจเหตุผลที่ชัดเจนว่าทำไมการป้อนข้อมูลผู้ใช้ที่เป็นอันตราย อันตราย ไม่จำเป็นต้องดูรายละเอียดการใช้งานมากนัก ซึ่งเป็นสาเหตุที่ทำให้ตระหนักว่ารายละเอียดการใช้งานเป็นเช่นนั้นซึ่งจะไม่มีการดำเนินการกับ SQL โดยเจตนาร้ายส่งข้อความถึงบ้าน คำตอบของคุณตอบคำถามอย่างไร (ตามที่ร้องขอ) แต่ฉันคิดว่าคนอื่น ๆ (เช่นฉัน) จะพอใจกับคำตอบสั้น ๆ ว่าทำไม?
N.Vegeta

ลองพิจารณาการตกแต่งนี้ซึ่งจะอธิบายถึงปมและไม่ใช่คำวิจารณ์โดยนัย (รู้สำนึกว่าใครเป็นผู้เขียนคำตอบที่ได้รับการยอมรับ)
N.Vegeta

5

เมื่อคุณสร้างและส่งคำสั่งที่เตรียมไว้ไปยัง DBMS คำสั่งนั้นจะถูกเก็บไว้เป็นแบบสอบถาม SQL สำหรับการดำเนินการ

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

ซึ่งหมายความว่าเป็นไปไม่ได้ที่จะทำการฉีด SQL โดยใช้คำสั่งที่เตรียมไว้แล้ว ลักษณะของข้อความที่เตรียมไว้และความสัมพันธ์กับ DBMS จะป้องกันสิ่งนี้


4

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

string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";

ตอนนี้ถ้าค่าในตัวแปร inoutusername เป็นอะไรเช่น 'หรือ 1 = 1 - ตอนนี้เคียวรีนี้จะกลายเป็น:

select * from table where username='a' or 1=1 -- and password=asda

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

Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

ดังนั้นคุณจะไม่สามารถส่งพารามิเตอร์อื่นเข้ามาได้ดังนั้นหลีกเลี่ยงการฉีด SQL ...


3

need not be correctly escapedวลีที่สำคัญคือ นั่นหมายความว่าคุณไม่ต้องกังวลกับคนที่พยายามจะใส่เครื่องหมายขีดคั่นเครื่องหมายวรรคตอนอัญประกาศ ฯลฯ ...

มันถูกจัดการทั้งหมดสำหรับคุณ


2
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

สมมติว่าคุณมีสิ่งนั้นใน Servlet ที่ถูกต้อง หากผู้มุ่งร้ายส่งผ่านค่าที่ไม่ดีสำหรับ 'ตัวกรอง' คุณอาจแฮ็คฐานข้อมูลของคุณ


0

สาเหตุหลัก # 1 - ปัญหาตัวคั่น

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

Root Cause # 2 - ธรรมชาติของมนุษย์ผู้คนมีฝีมือและบางคนมีฝีมือเป็นอันตราย และทุกคนทำผิดพลาด

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

แบบสอบถามที่มีโครงสร้างแก้ไขสาเหตุของการฉีด SQL ได้อย่างไร

แบบสอบถามที่มีโครงสร้างแก้ปัญหาตัวคั่นโดยใส่คำสั่ง sql ในหนึ่งคำสั่งและวางข้อมูลในคำสั่งการเขียนโปรแกรมแยกต่างหาก งบการเขียนโปรแกรมสร้างการแยกที่จำเป็น

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

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

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