SQL Injection là gì?
SQL Injection là một kỹ thuật chèn code được sử dụng trong tấn công các ứng dụng chứa dữ liệu (data-driven). Trong đó, các lệnh SQL độc hại được chèn vào trong một entry field để thực thi (chẳng hạn như để kết xuất nội dung Cơ sở dữ liệu cho hacker). SQL Injection phải khai thác lỗ hổng bảo mật trong phần mềm của ứng dụng.
SQL Injection chủ yếu được biết đến như một vector tấn công dành cho các trang web. Nhưng nó cũng có thể được dùng để tấn công bất kỳ loại cơ sở dữ liệu SQL nào. Các cuộc tấn công SQL Injection cho phép hacker giả mạo danh tính, xáo trộn dữ liệu. Hay gây ra các vấn đề như làm mất hiệu lực giao dịch, thay đổi số dư, tiết lộ hay phá hủy dữ liệu trên hệ thống. Thậm chí là làm dữ liệu không khả dụng hoặc trở thành admin của server cơ sở dữ liệu.
>> Xem thêm: Thẻ Input trong HTML là gì? Tổng hợp các ví dụ cụ thể
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
Hacker sẽ gửi các data payload đến server và quan sát phản ứng, hành vi của server để tìm hiểu về cấu trúc của nó. Phương pháp này được gọi là Blind SQLi vì dữ liệu không được chuyển từ Cơ sở dữ liệu trang web đến hacker. Do đó hacker không thể nhìn thấy thông tin về cuộc tấn trong in-band.
Blind SQL injection dựa trên phản ứng và các hành vi hoạt động của server. Do đó chúng thường thực thi chậm hơn, nhưng có thể gây ảnh hưởng tương tự. Blind SQL injection có thể được phân loại thành:
- Boolean – Hacker gửi một truy vấn SQL đến Cơ sở dữ liệu, làm ứng dụng trả về một kết quả. Kết quả có thể khác nhau tùy vào truy vấn đúng hay sai. Dựa trên kết quả, thông tin trong HTTP response sẽ sửa đổi hoặc không. Sau đó, hacker có thể tìm hiểu xem thông báo tạo ra kết quả có đúng không.
- Time-based – Hacker sẽ gửi một truy vấn SQL đến Cơ sở dữ liệu, làm cho Cơ sở dữ liệu đợi (trong vài giây) trước khi có thể hoạt động. Sau đó, hacker có thể xem từ thời gian Cơ sở dữ liệu cần để phản hồi, một truy vấn là đúng hay sai. Dựa trên kết quả, một HTTP repsonse sẽ được tạo ra. Vì vậy hacker có thể tìm ra thông báo mà chúng đã sử dụng trả về đúng hay sai, không cần dựa 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.
Lời kết
Vietnix vừa chia sẻ cho 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ả, chúc bạn thành công!