PHP là ngôn ngữ lập trình web phía máy chủ phổ biến nhất thế giới. Theo dữ liệu của W3Techs từ tháng 4 năm 2019, 79% các trang web được cung cấp bởi PHP. Trong số các trang web đó có Facebook, Yahoo và Wikipedia. Vì PHP rất phổ biến, nên bảo mật PHP là rất cần thiết và số lượng ứng dụng PHP dễ bị tổn thương là lớn. Hầu hết các ứng dụng web PHP đều chia sẻ các phần của mã hoặc tập lệnh với các ứng dụng web khác. Nếu đoạn mã được chia sẻ là dễ bị tổn thương, tất cả các ứng dụng đang sử dụng nó cũng dễ bị tổn thương. Hầu hết các lỗ hổng là kết quả của thói quen mã hóa xấu hoặc thiếu nhận thức bảo mật ứng dụng PHP giữa các nhà phát triển. Nguyên nhân chính là thực tế là đầu vào của người dùng được coi là đáng tin cậy. Khi bạn viết mã, bạn phải áp dụng hai quy trình chính: xác thực và vệ sinh. Nếu bạn thực hiện cả hai quy trình này cho dữ liệu người dùng, bạn đảm bảo rằng những gì được xử lý và thực hiện là hợp lệ và đáp ứng các tiêu chí được chỉ định. Bạn cũng phải đảm bảo rằng dữ liệu đầu ra HTML được thoát ra để không có mã độc nào được thực thi trong trường hợp kẻ tấn công đã tiêm nó vào nội dung. Nếu bạn làm theo các quy trình đơn giản và cơ bản nhất định cho mỗi trang web, bạn sẽ giảm thiểu đáng kể khả năng tiếp xúc với một vấn đề bảo mật quan trọng.Nguyên nhân của các lỗ hổng
Lập trình hướng đối tượng (OOP) đóng một vai trò lớn trong việc áp dụng các quy trình bảo mật PHP. Mã tái sử dụng được viết tốt có thể làm tăng đáng kể tính bảo mật tổng thể của một hệ thống. Nó đảm bảo rằng quy trình xử lý dữ liệu tương tự luôn được tuân theo.
SQL tiêm vào PHP
SQL Injection là một trong những lỗ hổng ứng dụng web nguy hiểm nhất. Nó luôn được xếp hạng là số một bởi OWASP và theo dõi các lỗ hổng khác như kịch bản chéo trang (XSS) hoặc giả mạo yêu cầu chéo trang (CSRF). Nếu bạn chèn đầu vào người dùng trực tiếp vào truy vấn SQL (không được đánh giá cao/không được xác định), kẻ tấn công có thể tự thao tác với chính truy vấn. Họ có thể buộc nó trả về một kết quả khác với dự kiến.
Một cuộc tấn công SQL thành công có thể dẫn đến vi phạm dữ liệu, có thể hiển thị tên người dùng, mật khẩu, địa chỉ email, thông tin thẻ tín dụng và dữ liệu nhạy cảm khác. Trong một số trường hợp, ngay cả một cuộc tấn công phổ biến cũng có thể dẫn đến sự thỏa hiệp của toàn bộ máy chủ web.
Mẫu mã không an toàn
Trong ví dụ mã nguồn sau đây, tham số article được chuyển cho truy vấn theo cách không an toàn:
$articleid = $_GET['article']; $query = "SELECT * FROM articles WHERE articleid = '$articleid'";Người dùng độc hại có thể gửi một giá trị được chế tạo đặc biệt sẽ được bao gồm trong truy vấn SQL trước khi truy vấn được thực thi. Ví dụ:
1'+union+select+1,version(),3'Truy vấn trở thành:
$query = "SELECT * FROM articles WHERE articleid = '1'+union+select+1,version(),3''";Kẻ tấn công có thể sử dụng các yêu cầu tương tự như mã trên để liệt kê tất cả các bảng/cột của cơ sở dữ liệu và có quyền truy cập vào thông tin nhạy cảm.
Giải pháp cho vấn đề này là sử dụng các truy vấn SQL được tham số hóa (câu lệnh đã chuẩn bị). Nếu bạn sử dụng các truy vấn được tham số hóa, bạn cho cơ sở dữ liệu biết, phần nào là truy vấn và dữ liệu nào (đầu vào người dùng) vì bạn gửi chúng trong hai yêu cầu riêng biệt. Điều này giúp loại bỏ khả năng trộn đầu vào của người dùng và truy vấn SQL.
Bạn nên sử dụng các đối tượng dữ liệu PHP (PDO) để thực thi các câu lệnh đã chuẩn bị trong các tập lệnh PHP của bạn. PDO được bao gồm trong phiên bản mới nhất của PHP. Nó được giới thiệu trong PHP 5.1 và có sẵn dưới dạng phần mở rộng PECL trong PHP 5.0. Nó không thể truy cập được cho các phiên bản PHP trước đó.
Tránh sử dụng tiện ích mở rộng MySQL và MySQLI trong mã PHP của bạn. Chúng không được dùng thuốc/lỗi thời nhưng vẫn thường được sử dụng và bạn có thể viết lại mã để sử dụng PDO thay thế.
Mẫu mã an toàn
Trong tệp PHP mẫu này, bạn không truyền trực tiếp user_id cho truy vấn. Thay vào đó, bạn thay thế nó bằng một trình giữ chỗ. Cơ sở dữ liệu phụ trợ sẽ biết những gì để thay thế trình giữ chỗ bằng khi bạn chạy chức năng execute().
// User ID must not be empty, must be numeric and must be less than 5 characters long if((!empty($_GET['user_id'])) && (is_numeric($_GET['user_id'])) && (mb_strlen($_GET['user_id'])<5)) { $servername = "localhost"; $username = "username"; $password = "password"; $database = "dbname"; // Establish a new connection to the SQL server using PDO try { $conn = new PDO("mysql:host=$servername;dbname=$database", $username, $password); // Assign user input to the $user_id variable $user_id = $_GET['user_id']; // Prepare the query and set a placeholder for user_id $sth = $conn->prepare('SELECT user_name, user_surname FROM users WHERE user_id=?'); // Execute the query by providing the user_id parameter in an array $sth->execute(array($user_id)); // Fetch all matching rows $user = $sth->fetch(); // If there is a matching user, display their info if(!empty($user)) { echo "Welcome ".$user['user_name']." ".$user['user_surname']; } else { echo "No user found"; } // Close the connection $dbh = null; } catch(PDOException $e) { echo "Connection failed."; } } else { echo "User ID not specified or invalid."; }Để biết thêm chi tiết về việc ngăn ngừa tiêm SQL trong PHP, hãy xem bài viết sau: Ngăn chặn tiêm SQL.