Cái đúng cách để 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 giải thích dưới dạng các lệnh của trình phân tích cú pháp SQL. Có thể tạo 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 đầy đủ hiểu 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. Đây là 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:
-
Sử dụng PDO (đối với bất kỳ trình điều khiển cơ sở dữ liệu được hỗ trợ nào):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
-
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à lựa chọn phổ biến.
Thiết lập kết nối đúng cách
Lưu ý rằng khi sử dụng PDO để truy cập cơ sở dữ liệu MySQL real các câu lệnh chuẩn bị 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ị. Ví dụ về cách tạo kết nối bằng PDO là:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Trong ví dụ trên, chế độ lỗi không thực sự cần thiết, nhưng bạn nên thêm chế độ này . Bằng cách này, tập lệnh sẽ không dừng lại với Fatal Error
khi có sự cố. Và nó cho nhà phát triển cơ hội để catch
bất kỳ (các) lỗi nào throw
n là PDOException
s.
bắt buộc là gì , tuy nhiên, là setAttribute()
đầu tiên dòng này cho PDO biết để tắt các câu lệnh đã chuẩn bị được mô phỏng và sử dụng real các báo cáo đã chuẩn bị sẵn. Đ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 (không cho kẻ tấn công có cơ hội đưa SQL độc hại vào).
Mặc dù bạn có thể đặt charset
trong các tùy chọn của hàm tạo, đ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) đã im lặng bỏ qua tham số bộ ký tự
trong DSN.
Giải thích
Câu lệnh SQL mà bạn chuyển tới prepare
được phân tích cú pháp và biên dịch bởi máy chủ cơ sở dữ liệu. Bằng cách chỉ định các tham số (?
hoặc một tham số được đặt tên như :name
trong ví dụ trên) bạn cho công cụ cơ sở dữ liệu biết nơi bạn muốn lọc. 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ố 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 sẽ hạn chế rủi ro kết thúc với điều gì đó mà 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 $name
biến chứa 'Sarah'; DELETE FROM employees
kết quả 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ị sẵn 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, mang lại cho bạn một số lợi ích về 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';
}