Câu hỏi

Nếu nhập từ PHP một chuỗi không được xử lý, thì sẽ có những cuộc tấn công SQL Injection như ở ví dụ sau:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Người dùng có thể nhập một cái gì đó như value '); DROP TABLE table;--, và truy vấn sql sẽ nhìn như sau:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Có thể làm gì để ngăn chặn điều này xảy ra?

Câu trả lời

Cách chính xác để tránh các cuộc tấn công SQL injection, bất kể bạn sử dụng cơ sở dữ liệu nào, là tách dữ liệu khỏi SQL , để dữ liệu vẫn là dữ liệu và sẽ không bao giờ được trình phân tích cú pháp SQL hiểu là lệnh. Có thể tạo một câu lệnh SQL với các phần dữ liệu được định dạng chính xác, nhưng nếu bạn không hiểu đầy đủ các chi tiết, bạn nên luôn sử dụng các câu lệnh đã chuẩn bị sẵn và các truy vấn được tham số hóa. Các câu lệnh SQL được gửi đến và phân tích cú pháp bởi máy chủ cơ sở dữ liệu riêng biệt với bất kỳ tham số nào. Bằng cách này, kẻ tấn công không thể đưa SQL độc hại vào.

Về cơ bản, bạn có hai lựa chọn để đạt được điều này:

1. Sử dụng PDO (cho bất kỳ trình điều khiển cơ sở dữ liệu được hỗ trợ):

$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->execute([ 'name' => $name ]);

foreach ($stmt as $row) {
    // Do something with $row
}

2. Sử dụng MySQLi (dành cho MySQL):

$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
$stmt->execute();

$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    // Do something with $row
}

Nếu bạn đang kết nối với cơ sở dữ liệu không phải MySQL, có một tùy chọn thứ hai dành riêng cho trình điều khiển mà bạn có thể tham khảo (ví dụ: pg_prepare()và pg_execute() cho PostgreSQL). PDO là tùy chọn phổ biến hơn.

Thiết lập kết nối chính xác

PDO

Lưu ý khi sử dụng PDO để truy vấn trong MYSQL, các câu lệnh sẽ không được sử dụng theo mặc định. Để khắc phục điều này, bạn phải tắt tính năng mô phỏng các câu lệnh đã chuẩn bị (emulation of prepared statements). Ví dụ về việc tạo kết nối bằng PDO là:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Trong ví dụ trên, chế độ báo lỗi không hoàn toàn cần thiết, nhưng bạn nên thêm nó vào . Bằng cách này, PDO sẽ thông báo cho bạn về tất cả các lỗi MySQL thông qua PDOException.

Tuy nhiên, điều bắt buộc là dòng đầu tiên , dòng setAttribute() này yêu cầu PDO vô hiệu hóa các câu lệnh chuẩn bị giả lập và sử dụng các câu lệnh chuẩn bị thực sự . Điều này đảm bảo rằng câu lệnh và các giá trị không được PHP phân tích cú pháp trước khi gửi nó đến máy chủ MySQL (cho phép kẻ tấn công không có cơ hội để đưa SQL độc hại vào).

Mặc dù bạn có thể đặt các charset tùy chọn trong hàm tạo, nhưng điều quan trọng cần lưu ý là các phiên bản PHP ‘cũ hơn’ (trước 5.3.6) đã âm thầm bỏ qua tham số bộ ký tự trong DSN.

MYSQLI

Đối với mysqli, chúng ta phải làm theo cùng một quy trình:

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting
$dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
$dbConnection->set_charset('utf8mb4'); // charset

Giải thích

Câu lệnh SQL mà bạn chuyển đến prepare được máy chủ cơ sở dữ liệu phân tích cú pháp và biên dịch. Bằng cách chỉ định các tham số (một ? hoặc một tham số được đặt tên :nametrong như  ví dụ ở trên), bạn cho cơ sở dữ liệu biết nơi bạn muốn điền tham số. Sau đó, khi bạn gọi execute, câu lệnh đã chuẩn bị được kết hợp với các giá trị tham số mà bạn chỉ định.

Điều quan trọng ở đây là các giá trị tham số được kết hợp với câu lệnh đã biên dịch, không phải là một chuỗi SQL. SQL injection hoạt động bằng cách lừa tập lệnh bao gồm các chuỗi độc hại khi nó tạo SQL để gửi đến cơ sở dữ liệu. Vì vậy, bằng cách gửi SQL thực tế riêng biệt với các tham số, bạn hạn chế rủi ro kết thúc với một cái gì đó bạn không có ý định.

Bất kỳ tham số nào bạn gửi khi sử dụng một câu lệnh đã chuẩn bị sẽ chỉ được coi là chuỗi (mặc dù công cụ cơ sở dữ liệu có thể thực hiện một số tối ưu hóa nên tất nhiên các tham số cũng có thể kết thúc dưới dạng số). Trong ví dụ trên, nếu $namebiến chứa ‘Sarah’; DELETE FROM employeeskết quả sẽ chỉ đơn giản là tìm kiếm chuỗi “‘Sarah’; DELETE FROM employees”và bạn sẽ không kết thúc với một bảng trống .

Một lợi ích khác của việc sử dụng các câu lệnh đã chuẩn bị là nếu bạn thực hiện cùng một câu lệnh nhiều lần trong cùng một phiên, nó sẽ chỉ được phân tích cú pháp và biên dịch một lần, giúp bạn tăng tốc độ.

Ồ, và vì bạn đã hỏi về cách thực hiện điều đó cho một đoạn chèn, đây là một ví dụ (sử dụng PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

Có thể sử dụng các câu lệnh đã chuẩn bị cho các truy vấn động không?

Mặc dù bạn vẫn có thể sử dụng các câu lệnh đã chuẩn bị sẵn cho các tham số truy vấn, nhưng bản thân cấu trúc của truy vấn động không thể được tham số hóa và một số tính năng truy vấn nhất định không thể được tham số hóa.

Đối với những trường hợp cụ thể này, điều tốt nhất nên làm là sử dụng bộ lọc danh sách trắng hạn chế các giá trị có thể có.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

stackoverflow

Bài viết gốc: https://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php

Leave a comment

Your email address will not be published. Required fields are marked *