Làm thế nào có thể chuẩn bị các câu lệnh bảo vệ khỏi các cuộc tấn công SQL SQL?


172

Làm thế nào để các câu lệnh được chuẩn bị giúp chúng tôi ngăn chặn các cuộc tấn công tiêm nhiễm SQL ?

Wikipedia nói:

Các câu lệnh được chuẩn bị có khả năng phục hồi chống lại việc tiêm SQL, bởi vì các giá trị tham số, được truyền sau này bằng một giao thức khác, không cần phải thoát chính xác. Nếu mẫu câu lệnh gốc không xuất phát từ đầu vào bên ngoài, SQL có thể xảy ra.

Tôi không thể thấy lý do rất tốt. Điều gì sẽ là một lời giải thích đơn giản bằng tiếng Anh dễ dàng và một số ví dụ?

Câu trả lời:


290

Ý tưởng rất đơn giản - truy vấn và các dữ liệu được gửi đến máy chủ cơ sở dữ liệu riêng biệt .
Đó là tất cả.

Nguyên nhân của vấn đề tiêm SQL là trong việc trộn mã và dữ liệu.

Trong thực tế, truy vấn SQL của chúng tôi là một chương trình hợp pháp . Và chúng tôi đang tạo ra một chương trình như vậy một cách linh hoạt, thêm một số dữ liệu một cách nhanh chóng. Do đó, dữ liệu có thể can thiệp vào mã chương trình và thậm chí thay đổi nó, vì mọi ví dụ tiêm SQL đều hiển thị nó (tất cả các ví dụ trong PHP / Mysql):

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

sẽ tạo ra một truy vấn thông thường

SELECT * FROM users where id=1

trong khi mã này

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

sẽ tạo ra một chuỗi độc hại

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

Nó hoạt động vì chúng tôi đang thêm dữ liệu trực tiếp vào phần thân chương trình và nó trở thành một phần của chương trình, do đó dữ liệu có thể thay đổi chương trình và tùy thuộc vào dữ liệu được truyền, chúng tôi sẽ có đầu ra thông thường hoặc usersxóa bảng .

Mặc dù trong trường hợp các tuyên bố đã được chuẩn bị, chúng tôi không thay đổi chương trình của mình, nó vẫn còn nguyên vẹn
Đó là điểm chính.

Chúng tôi đang gửi một chương trình đến máy chủ đầu tiên

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

trong đó dữ liệu được thay thế bởi một số biến được gọi là tham số hoặc giữ chỗ.

Lưu ý rằng chính xác cùng một truy vấn được gửi đến máy chủ, không có bất kỳ dữ liệu nào trong đó! Và sau đó chúng tôi sẽ gửi dữ liệu với yêu cầu thứ hai , về cơ bản được tách ra khỏi chính truy vấn:

$db->execute($data);

vì vậy nó không thể thay đổi chương trình của chúng tôi và gây hại.
Khá đơn giản - phải không?

Điều duy nhất tôi phải thêm luôn luôn bị bỏ qua trong mọi hướng dẫn:

Các câu lệnh được chuẩn bị có thể chỉ bảo vệ các dữ liệu bằng chữ , nhưng không thể được sử dụng với bất kỳ phần truy vấn nào khác.
Vì vậy, một khi chúng ta phải thêm, giả sử, một định danh động - ví dụ như tên trường - các câu lệnh được chuẩn bị không thể giúp chúng ta. Tôi đã giải thích vấn đề gần đây , vì vậy tôi sẽ không lặp lại.


2
"Ví dụ: theo mặc định, PDO không sử dụng các câu lệnh đã chuẩn bị" - điều đó không hoàn toàn chính xác, bởi vì PDO chỉ mô phỏng các câu lệnh được chuẩn bị cho các trình điều khiển không hỗ trợ tính năng đó.
thông

3
@ zaq178miami: "PDO chỉ mô phỏng các câu lệnh được chuẩn bị cho các trình điều khiển không hỗ trợ tính năng này" - không chính xác. MySQL đã hỗ trợ các báo cáo được chuẩn bị từ khá lâu rồi. Trình điều khiển PDO là tốt. Tuy nhiên, các truy vấn MySQL vẫn được PDO chuẩn bị theo mặc định, lần trước tôi đã kiểm tra.
cHao

9
Có gì khác nhau giữa $spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";, so với: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> $db->execute($data);. Họ sẽ không làm điều tương tự?
Juha Untinen

14
@Juha Untinen Dữ liệu có thể là bất cứ điều gì. Nó sẽ không phân tích dữ liệu. Đó là DATA không phải là lệnh. Vì vậy, ngay cả khi dữ liệu $ chứa các lệnh sql, nó sẽ không được thực thi. Ngoài ra, nếu id là một số, thì nội dung chuỗi sẽ tạo báo cáo hoặc giá trị 0.
Soley

21

Đây là SQL để thiết lập một ví dụ:

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);

Lớp Tiêm dễ bị tổn thương khi tiêm SQL. Truy vấn được dán động cùng với đầu vào của người dùng. Mục đích của truy vấn là hiển thị thông tin về Bob. Tiền lương hoặc tiền thưởng, dựa trên đầu vào của người dùng. Nhưng người dùng độc hại thao túng đầu vào làm hỏng truy vấn bằng cách xử lý tương đương với 'hoặc đúng' với mệnh đề where để mọi thứ được trả về, bao gồm cả thông tin về Aaron được cho là bị ẩn.

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"));
        }
    }
}

Chạy này, trường hợp đầu tiên là với việc sử dụng bình thường và trường hợp thứ hai với tiêm độc hại:

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

Bạn không nên xây dựng các câu lệnh SQL của mình bằng cách nối chuỗi đầu vào của người dùng. Nó không chỉ dễ bị tiêm mà còn có ý nghĩa lưu trữ trên máy chủ (câu lệnh thay đổi, do đó ít có khả năng bị lỗi bộ đệm câu lệnh SQL trong khi ví dụ liên kết luôn chạy cùng một câu lệnh).

Dưới đây là một ví dụ về Binding để tránh loại tiêm này:

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"));
        }
    }
}

Chạy mã này với cùng một đầu vào như ví dụ trước cho thấy mã độc không hoạt động vì không có chuỗi thanh toán khớp với chuỗi đó:

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=?

Việc sử dụng một câu lệnh được chuẩn bị từ chương trình kết nối với cơ sở dữ liệu có tác dụng tương tự như việc sử dụng một câu lệnh được chuẩn bị đó là một phần của db không? Ví dụ, Postgres có tuyên bố được chuẩn bị riêng và việc sử dụng nó có ngăn chặn SQL SQL không? postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas

@Celeritas Tôi không có câu trả lời dứt khoát cho Postgresql. Nhìn vào các tài liệu, nó xuất hiện hiệu ứng là như nhau. PREPAREtạo một câu lệnh có tên cố định đã được phân tích cú pháp (tức là câu lệnh sẽ không thay đổi nữa bất kể đầu vào) trong khi EXECUTEsẽ chạy câu lệnh có tên ràng buộc các tham số. Vì PREPAREchỉ có thời lượng phiên, nên nó thực sự có nghĩa là vì lý do hiệu suất, không phải để ngăn chặn việc tiêm thông qua các tập lệnh psql. Để truy cập psql, có thể cấp quyền cho các thủ tục được lưu trữ và liên kết các tham số trong procs.
Glenn

@Celeritas Tôi đã thử mã trên bằng PostgreSQL 11.1 trên x86_64 và ví dụ SQLi ở trên đã hoạt động.
Krishna Pandey

15

Về cơ bản, với các câu lệnh được chuẩn bị, dữ liệu đến từ một hacker tiềm năng được coi là dữ liệu - và không có cách nào nó có thể được trộn lẫn với SQL ứng dụng của bạn và / hoặc được hiểu là SQL (có thể xảy ra khi dữ liệu được truyền trực tiếp vào ứng dụng SQL).

Điều này là do các câu lệnh đã chuẩn bị "chuẩn bị" truy vấn SQL trước để tìm một kế hoạch truy vấn hiệu quả và gửi các giá trị thực tế có lẽ đến từ một biểu mẫu sau - tại thời điểm đó truy vấn thực sự được thực thi.

Thêm thông tin tuyệt vời ở đây:

Báo cáo đã chuẩn bị và SQL Injection


6

Tôi đọc qua các câu trả lời và vẫn cảm thấy cần phải nhấn mạnh điểm chính làm sáng tỏ bản chất của Tuyên bố chuẩn bị. Xem xét hai cách để truy vấn cơ sở dữ liệu của một người có liên quan đến đầu vào của người dùng:

Cách tiếp cận ngây thơ

Một kết nối đầu vào của người dùng với một số chuỗi SQL một phần để tạo ra một câu lệnh SQL. Trong trường hợp này, người dùng có thể nhúng các lệnh SQL độc hại, sau đó sẽ được gửi đến cơ sở dữ liệu để thực thi.

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

Ví dụ: đầu vào của người dùng độc hại có thể dẫn đến SQLStringbằng"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

Do người dùng độc hại, SQLStringchứa 2 câu lệnh, trong đó câu lệnh thứ 2 ( "DROP TABLE CUSTOMERS") sẽ gây hại.

Báo cáo chuẩn bị

Trong trường hợp này, do tách biệt truy vấn & dữ liệu, đầu vào của người dùng không bao giờ được coi là một câu lệnh SQL và do đó không bao giờ được thực thi . Vì lý do này, bất kỳ mã SQL độc hại nào được tiêm sẽ không gây hại. Vì vậy, "DROP TABLE CUSTOMERS"sẽ không bao giờ được thực hiện trong trường hợp trên.

Tóm lại, với các câu lệnh được chuẩn bị, mã độc được giới thiệu thông qua đầu vào của người dùng sẽ không được thực thi!


Có thật không? Câu trả lời được chấp nhận không nói chính xác điều đó?
Ý thức chung của bạn

@ Ý thức chung của bạn Câu trả lời được chấp nhận chứa rất nhiều thông tin có giá trị nhưng nó khiến tôi tự hỏi những chi tiết thực hiện của việc tách dữ liệu và truy vấn đòi hỏi gì. Trong khi tập trung vào điểm mà dữ liệu được tiêm độc hại (nếu có) sẽ không bao giờ được thực hiện đánh vào đầu đinh.
N.V Dona

Và "chi tiết thực hiện" nào được cung cấp trong câu trả lời của bạn không có ở đó?
Ý thức chung của bạn

nếu bạn cố gắng xem tôi đến từ đâu, bạn sẽ nhận ra rằng quan điểm của tôi là như sau: Mong muốn ngắn gọn để xem chi tiết triển khai xuất phát từ nhu cầu hiểu lý do rõ ràng tại sao đầu vào của người dùng độc hại sẽ không gây ra bất kỳ làm hại. Không quá nhiều để xem chi tiết thực hiện. Đó là lý do tại sao nhận ra rằng các chi tiết triển khai là như vậy, không có lúc nào SQL được nhập độc hại sẽ được thực thi, gửi tin nhắn về nhà. Câu trả lời của bạn trả lời câu hỏi, làm thế nào (theo yêu cầu)?, Nhưng tôi tưởng tượng những người khác (như tôi) sẽ hài lòng với câu trả lời ngắn gọn tại sao?
N.V Dona

Hãy coi đây là một sự phong phú làm sáng tỏ mấu chốt, và không phải là một lời chỉ trích ngụ ý (recenlty nhận ra ai là tác giả của phản ứng được chấp nhận).
N.V Dona

5

Khi bạn tạo và gửi một câu lệnh đã chuẩn bị tới DBMS, nó sẽ được lưu dưới dạng truy vấn SQL để thực thi.

Sau đó, bạn liên kết dữ liệu của mình với truy vấn sao cho DBMS sử dụng dữ liệu đó làm tham số truy vấn để thực hiện (tham số hóa). DBMS không sử dụng dữ liệu bạn liên kết như một phần bổ sung cho truy vấn SQL đã được biên dịch; nó chỉ đơn giản là dữ liệu.

Điều này có nghĩa là về cơ bản không thể thực hiện SQL SQL bằng cách sử dụng các câu lệnh đã chuẩn bị. Bản chất của các tuyên bố đã chuẩn bị và mối quan hệ của chúng với DBMS ngăn chặn điều này.


4

Trong SQL Server , sử dụng câu lệnh được chuẩn bị chắc chắn là bằng chứng tiêm vì các tham số đầu vào không tạo thành truy vấn. Nó có nghĩa là truy vấn được thực hiện không phải là một truy vấn động. Ví dụ về một câu lệnh SQL dễ bị tổn thương.

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

Bây giờ nếu giá trị trong biến inoutusername giống như 'hoặc 1 = 1 - thì truy vấn này sẽ trở thành:

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

Và phần còn lại được nhận xét sau --, vì vậy nó không bao giờ được thực thi và bỏ qua như sử dụng ví dụ tuyên bố đã chuẩn bị như dưới đây.

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();

Vì vậy, trong thực tế, bạn không thể gửi một tham số khác, do đó tránh được SQL SQL ...


3

Cụm từ chính là need not be correctly escaped. Điều đó có nghĩa là bạn không phải lo lắng về việc mọi người cố gắng ném vào dấu gạch ngang, dấu nháy đơn, dấu ngoặc kép, v.v ...

Đó là tất cả xử lý cho bạn.


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

Giả sử bạn có điều đó trong một Servlet bạn đúng. Nếu một người độc ác chuyển một giá trị xấu cho 'bộ lọc', bạn có thể hack cơ sở dữ liệu của mình.


0

Nguyên nhân gốc rễ số 1 - Vấn đề phân định

Có thể tiêm Sql bởi vì chúng tôi sử dụng dấu ngoặc kép để phân định chuỗi và cũng là một phần của chuỗi, khiến đôi khi không thể diễn giải chúng. Nếu chúng ta có các dấu phân cách không thể được sử dụng trong dữ liệu chuỗi, việc tiêm sql sẽ không bao giờ xảy ra. Giải quyết vấn đề phân định loại bỏ vấn đề tiêm sql. Cấu trúc truy vấn làm điều đó.

Nguyên nhân gốc rễ số 2 - Bản chất con người, Con người xảo quyệt và Một số người xảo quyệt là độc hại và tất cả mọi người đều phạm sai lầm

Nguyên nhân gốc rễ khác của tiêm sql là bản chất con người. Mọi người, kể cả lập trình viên, mắc lỗi. Khi bạn mắc lỗi trên một truy vấn có cấu trúc, nó không làm cho hệ thống của bạn dễ bị tấn công sql. Nếu bạn không sử dụng các truy vấn có cấu trúc, các lỗi có thể tạo ra lỗ hổng tiêm sql.

Các truy vấn có cấu trúc giải quyết các nguyên nhân gốc của SQL Injection

Các truy vấn có cấu trúc giải quyết vấn đề Delimiter, bằng cách đặt các lệnh sql trong một câu lệnh và đưa dữ liệu vào một câu lệnh lập trình riêng biệt. Lập trình báo cáo tạo sự tách biệt cần thiết.

Các truy vấn có cấu trúc giúp ngăn ngừa lỗi của con người tạo ra các lỗ hổng bảo mật quan trọng. Đối với con người mắc lỗi, tiêm sql không thể xảy ra khi sử dụng truy vấn cấu trúc. Có nhiều cách để ngăn chặn tiêm sql không liên quan đến các truy vấn có cấu trúc, nhưng lỗi thông thường của con người trong các phương pháp đó thường dẫn đến ít nhất một số phơi nhiễm với tiêm sql. Truy vấn cấu trúc không an toàn từ tiêm sql. Bạn có thể mắc tất cả các lỗi trên thế giới, hầu như, với các truy vấn có cấu trúc, giống như bất kỳ chương trình nào khác, nhưng không có lỗi nào bạn có thể mắc phải có thể bị biến thành một ssstem bị tiêm bởi sql. Đó là lý do tại sao mọi người muốn nói đây là cách đúng đắn để ngăn ngừa tiêm sql.

Vì vậy, bạn có nó, các nguyên nhân của tiêm sql và các truy vấn có cấu trúc tự nhiên làm cho chúng không thể khi chúng được sử dụng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.