SQL Injection là gì? Làm thế nào để phòng chống SQL Injection
Thịnh Văn Hạnh 06/06/2023 1092 Lượt xem Chia sẻ bài viết
SQL Injection là một kỹ thuật tấn công thông qua việc chèn các câu lệnh SQL bất hợp pháp vào các truy vấn SQL được tạo bởi ứng dụng web. Điều này sẽ gây ra cản trở với việc truy cập web bình thường, không những thế bạn còn có nguy cơ bị đánh cắp dữ liệu cho các mục đích xấu. Cùng BKNS tìm hiểu SQL Injection là gì, các cách phòng chống SQL Injection qua bài viết này nhé.
Tóm Tắt Bài Viết
SQL Injection là gì?
SQL Injection là một kỹ thuật tấn công phổ biến được sử dụng để xâm nhập vào các ứng dụng chứa dữ liệu, đặc biệt là các trang web. Trong tấn công này, kẻ tấn công chèn các đoạn mã SQL độc hại vào các trường nhập liệu để thực thi các lệnh SQL không được ủy quyền. SQL Injection khai thác các lỗ hổng bảo mật trong phần mềm của ứng dụng, cho phép kẻ tấn công giả mạo danh tính, xáo trộn dữ liệu hoặc thực hiện các hoạt động không mong muốn.
Mặc dù SQL Injection thường được sử dụng để tấn công các trang web, nhưng nó cũng có thể được áp dụng vào bất kỳ cơ sở dữ liệu SQL nào. Các cuộc tấn công SQL Injection cho phép hacker thực hiện nhiều hành động, bao gồm giả mạo danh tính, xáo trộn dữ liệu, làm mất hiệu lực các giao dịch, thay đổi số dư, tiết lộ hoặc phá hủy dữ liệu trên hệ thống. Thậm chí, kẻ tấn công có thể làm cho dữ liệu trở nên không khả dụng hoặc chiếm quyền quản trị của máy chủ cơ sở dữ liệu.
Các loại SQL Injection
SQL Injection có ba loại: In-band SQLi (Classic), Inferential SQLi (Blind) và Out-of-band SQLi. Bạn có thể phân loại các kiểu SQL injection dựa trên phương pháp sử dụng để truy cập dữ liệu backend, hoặc khả năng gây hại của chúng.
In-band SQLi
Kẻ tấn công sử dụng cùng một kênh liên lạc để khởi động các cuộc tấn công và thu thập các kết quả. Tính đơn giản và hiệu quả của In-band SQLi khiến nó trở thành một trong những kiểu tấn công SQLi phổ biến nhất hiện nay. Có hai biến thể phụ của phương pháp này:
- Error-based SQLi – Hacker sẽ thực hiện các hành động làm cơ sở dữ liệu tạo ra thông báo lỗi. Hacker có thể dùng dữ liệu được cung cấp bởi các thông báo lỗi này để thu thập thông tin về cấu trúc của cơ sở dữ liệu.
- Union-based SQLi – Kỹ thuật này lợi dụng toán tử UNION SQL để kết hợp nhiều câu lệnh được tạo bởi Cơ sở dữ liệu để nhận được một HTTP response. Response này có thể chứa dữ liệu mà kẻ tấn công có thể sử dụng.
Inferential (Blind) SQLi
Kẻ tấn công sẽ gửi các tải dữ liệu đến máy chủ và quan sát phản ứng và hành vi của máy chủ để khám phá cấu trúc của nó. Phương pháp này được gọi là Blind SQL Injection vì dữ liệu không được chuyển trực tiếp từ cơ sở dữ liệu của trang web đến kẻ tấn công. Do đó, kẻ tấn công không thể trực tiếp xem thông tin liên quan đến cuộc tấn công trong thời điểm đó.
Blind SQL Injection dựa trên phản ứng và hành vi của máy chủ. Vì vậy, chúng thường thực thi chậm hơn, nhưng có thể gây ra tác động tương tự. Blind SQL Injection có thể được phân loại thành hai loại chính:
1. Boolean: Kẻ tấn công gửi một truy vấn SQL đến cơ sở dữ liệu và dựa trên kết quả truy vấn, ứng dụng sẽ trả về một kết quả khác nhau tùy thuộc vào truy vấn đúng hay sai. Dựa trên kết quả này, thông tin trong phản hồi HTTP sẽ được thay đổi hoặc không. Kẻ tấn công có thể dựa vào thông tin này để xác định xem thông báo trả về có chính xác hay không.
2. Time-based: Kẻ tấn công gửi một truy vấn SQL đến cơ sở dữ liệu và làm cho cơ sở dữ liệu phải chờ đợi một khoảng thời gian trước khi phản hồi. Sau đó, kẻ tấn công có thể quan sát thời gian mà cơ sở dữ liệu mất để phản hồi, từ đó xác định xem một truy vấn là đúng hay sai. Dựa trên kết quả này, một phản hồi HTTP sẽ được tạo ra. Kẻ tấn công có thể tìm ra thông báo được sử dụng để trả về kết quả đúng hay sai mà không cần phụ thuộc vào dữ liệu từ cơ sở dữ liệu.
Out-of-band SQLi
Hacker chỉ có thể thực hiện hình thức này khi có một số tính năng nhất định được kích hoạt trên server Cơ sở dữ liệu được ứng dụng web sử dụng. Hình thức này chủ yếu được dùng để thay thế cho khác kỹ thuật in-band và inferential SQLi.
Out-of-band SQLi được thực hiện khi hacker không thể sử dụng cùng một kênh để khởi động tấn công và thu thập thông tin. Hoặc do server quá chậm, không ổn định tấn thực hiện hành động. Các kỹ thuật này dựa vào khả năng server tạo ra các DNS hay HTTP request để chuyển dữ liệu cho kẻ tấn công.
Ví dụ về cách sử dụng SQL Injection
Kẻ tấn công muốn thực hiện SQL injection sẽ thao túng một truy vấn SQL tiêu chuẩn, để khai thác các lỗ hổng input không được xác thực trong Cơ sở dữ liệu. Có nhiều cách để thực hiện vector tấn công này. Sau đây là một số cách để vận hành SQLi:
Lấy ví dụ về input ở trên. Nó sẽ lấy thông tin cho một sản phẩm cụ thể, có thể được đổi thành
http://www.estore.com/items/items.asp?itemid=999 or 1=1
Và truy vấn SQL sẽ có dạng như sau:
Vì lệnh 1=1 là luôn đúng, nên truy vấn trả về tất cả tên và mô tả sản phẩm trong Cơ sở dữ liệu, ngay cả những tên mà bạn không đủ điều kiện truy cập.
Những hacker cũng có thể lợi dụng các ký tự được lọc không chính xác để thay đổi các lệnh SQL. Trong đó gồm cả việc sử dụng dấu chấm phẩy để tách hai trường.
Ví dụ, có input:
http://www.estore.com/items/iteams.asp?itemid=999; DROP TABLE USERS
Người dùng sẽ tạo truy vấn SQL như sau:
Lúc đó, toàn bộ Cơ sở dữ liệu có thể bị xóa.
Một cách khác để thao túng truy vấn SQL là sử dụng lệnh UNION SELECT
. Nó kết hợp hai truy vấn SELECT không liên quan để lấy dữ liệu từ các bảng Cơ sở dữ liệu khác nhau.
Ví dụ, có input:
http://www.estore.com/items/items.asp?itemid=999 UNION SELECT user-name, password FROM USERS
Input này sẽ tạo truy vấn SQL sau:
Bằng cách sử dụng lệnh UNION SELECT
, truy vấn này kết hợp request cho tên và mô tả mục item 999
với một request khác, pull tên và password cho mọi người dùng trong Cơ sở dữ liệu.
Cách ngăn chặn SQL Injection
Chúng ta cần biết được cách phòng chống SQL injection hiệu quả. Vì SQL Injection có vector chính là các kênh input của người dùng. Nên cách tiếp cận tốt nhất là kiểm soát và xem xét input của người dùng để theo dõi các kiểu tấn công. Các developer cũng có thể tránh các lỗ hổng bảo mật bằng cách áp dụng các cách phòng chống SQL injection sau:
Input Validation (Xác thực đầu vào)
Quá trình xác thực nhằm xác minh xem loại input do người dùng gửi có hợp lệ hay không. Xác thực đầu vào đảm bảo đó là kiểu, độ dài, định dạng… được chấp nhận. Chỉ các giá trị qua được xác thực mới có thể được xử lý. Nó giúp chống lại mọi lệnh được chèn vào trong chuỗi input.
Xác thực nên được áp dụng với các trường cho phép người dùng nhập đầu vào, mà bạn còn nên quan tâm đến các tình huống sau:
- Sử dụng biểu thức chính quy làm whitelist cho các dữ liệu có cấu trúc (chẳng hạn như tên, tuổi, thu nhập, zip code) để đảm bảo xác thực đầu vào hợp lệ.
- Trong trường hợp có một bộ giá trị cố định (như drop-down list), hãy xác định giá trị được trả về. Dữ liệu đầu vào phải khớp với một trong các tùy chọn được cung cấp.
Dưới đây là cách xác thực tên bảng:
switswitch ($tableName) {
case 'fooTable': return true;
case 'barTable': return true;
default: return new BadMessageException('unexpected value provided as table name');
}
Biến $tableName có thể được append trực tiếp, bây giờ nó được biết đến là một trong những giá trị hợp pháp cho tên của Table.
Đối với drop-down list, việc xác thực dữ liệu rất đơn giản. Giả sử bạn muốn người dùng chọn xếp hạng từ 1 đến 5, hãy đổi PHP code thành:
<?php
if(isset($_POST["selRating"]))
{
$number = $_POST["selRating"];
if((is_numeric($number)) && ($number > 0) && ($number < 6))
{
echo "Selected rating: " . $number;
}
else
echo "The rating has to be a number between 1 and 5!";
}
Bạn đã thêm hai check đơn giản:
- Phải là một số (hàm is_numeric())
- $number phải lớn hơn 0, nhỏ hơn 6. Do đó điểm có phạm vi từ 1 – 5.
Dữ liệu nhận được từ bên ngoài phải được xác thực. Quy tắc này không chỉ áp dụng cho input do người dùng Internet cung cấp, mà còn cho các nhà cung cấp, đối tác, cơ quan quản lý.
Parametrized queries (Tham số hóa truy vấn)
Các truy vấn tham số hóa là một phương tiện pre-compile lệnh SQL. Sau đó bạn có thể cung cấp các tham số để câu lệnh được thực thi.
Phương pháp này giúp Cơ sở dữ liệu có thể nhận ra mã và phân biệt nó với dữ liệu đầu vào.
Input của người dùng được trích dẫn tự động, và kiểu mã hóa này giúp giảm thiểu tấn công SQL injection.
Ta cũng có thể sử dụng các truy vấn tham số hóa với phần mở rộng MySQLi, nhưng PHP 5.1 đã trình bày một cách tiếp cận tốt hơn khi làm việc với CSDL: PHP Data Objects (PDO). PDO áp dụng các phương pháp đơn giản hóa việc sử dụng các truy vấn tham số hóa. Ngoài ra, nó làm cho code dễ đọc hơn và dễ di chuyển hơn, vì nó còn hoạt động trên một số Cơ sở dữ liệu khác, không chỉ mỗi MySQL.
Code này sử dụng PDO với các truy vấn được tham số hóa để ngăn chặn lỗ hổng SQL injection:
<?php
$id = $_GET['id'];
$db_connection = new PDO('mysql:host=localhost;dbname=sql_injection_example', 'dbuser', 'dbpasswd');
//preparing the query
$sql = "SELECT username
FROM users
WHERE id = :id";
$query = $db_connection->prepare($sql);
$query->bindParam(':id', $id);
$query->execute();
//getting the result
$query->setFetchMode(PDO::FETCH_ASSOC);
$result = $query->fetchColumn();
print(htmlentities($result));
Stored Procedures
Các stored procedures (SP) yêu cầu developer nhóm một hay nhiều lệnh SQL thành một đơn vị logic để có thể tạo một kế hoạch thực thi. Các lần thực thi tiếp theo cho phép các lệnh được tham số hóa tự động. Nói một cách đơn giản, nó là một loại code có thể được lưu trữ để sử dụng sau này.
Vì vậy, bất cứ khi nào bạn cần thực hiện truy vấn, thay vì viết đi viết lại thì có thể gọi một stored procedure.
Đây là một quá trình tạo một SP trong server MySQL. Ví dụ bạn có một bảng như sau:
CREATE TABLE `salary` (
`empid` int(11) NOT NULL,
`sal` int(11) DEFAULT NULL,
PRIMARY KEY (`empid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Giả sử có một nhân viên cần lấy dữ liệu tổng hợp về lương công ty từ Table đó. Trước tiên, bạn cần tạo một người dùng ‘tr’:
CREATE USER 'tr'@'localhost' IDENTIFIED BY 'mypass';
Người dùng đó sẽ chỉ cần quyền EXECUTE đối với lược đồ nơi có Table:
grant execute on hris.* to tr@`%`
SP sẽ được tạo như sau:
DELIMITER $$
CREATE PROCEDURE `avg_sal`(out avg_sal decimal)
BEGIN
select avg(sal) into avg_sal from salary;
END
Quá trình phát hành lệnh tạo một avg_sal
SP và nó sẽ được lưu trữ trong Cơ sở dữ liệu.
Để gọi một SP từ một ừng dụng PHP, bạn có thể sử dụng PDO:
$db_connection = new PDO('mysql:host=localhost;dbname=hris', 'tr', 'mypass');
$query = $db_connection->exec('call avg_sal(@out)');
$res = $query->query('select @out')->fetchAll();
print_r($res);
$res sẽ hiện thị mức lương trung bình theo yêu cầu người dùng. Sau đó, người dùng có thể thực hiện quá trình output với PHP.
Sử dụng Escaping
Luôn sử dụng các hàm character-escaping cho input do user cung cấp, được cấp bởi mỗi hệ thống quản lý Cơ sở dữ liệu (DBMS). Việc này giúp đảm bảo DBMS không bao giờ nhầm lẫn nó với lệnh SQL do developer cung cấp.
Ví dụ: dùng mysql_real_escape_string()
trong PHP để tránh các ký tự có thể dẫn đến lệnh SQL không mong muốn. Một phiên sửa đổi cho login bypass sẽ như sau:
$db_connection = mysqli_connect("localhost", "user", "password", "db");
$username = mysqli_real_escape_string($db_connection, $_POST['username']);
$password = mysqli_real_escape_string($db_connection, $_POST['password']);
$query = "SELECT * FROM users WHERE username = '" . $username. "' AND password = '" . $password . "'";
Trước đây, code của bạn có thể dễ bị thêm escape character (\) ở phía trước dấu ngoặc. Tuy nhiên, bằng mã trên bạn sẽ được bảo vệ khỏi người dùng bất hợp pháp và giảm thiểu SQL injection.
Kết luận
Trên đây là những gì BKNS chia sẻ đến bạn về các loại SQL Injection và những cách phòng chống vấn đề này. Hy vọng qua bài viết này bạn sẽ học thêm được nhiều kiến thức mới và biết cách ngăn chặn SQL Injection hiệu quả.
Đọc thêm các bài viết hữu ích khác tại BKNS.
> Có thể bạn quan tâm: