SQL injection là gì: Nguyên nhân và cách phòng chống hiệu quả

Sơ đồ phân loại các vector tấn công SQL injection theo kiểu truy vấn

SQL injection là kỹ thuật tấn công chèn mã SQL độc hại vào câu truy vấn của ứng dụng, khiến cơ sở dữ liệu thực thi lệnh mà lập trình viên không muốn. Hậu quả có thể từ lộ dữ liệu người dùng, xoá sạch bảng cho tới chiếm quyền kiểm soát toàn bộ máy chủ.

OWASP liệt kê SQL injection vào nhóm A03 trong bảng OWASP Top Ten, nghĩa là đây vẫn là một trong những lỗ hổng phổ biến nhất dù đã xuất hiện từ nhiều thập kỷ. Nguyên nhân gốc rễ luôn giống nhau: dữ liệu người dùng không được tách khỏi mã truy vấn.

Lỗ hổng nằm ở đâu

Hãy xem một câu truy vấn đăng nhập viết bằng cách nối chuỗi trong PHP:

$name = $_POST["username"];
$query = "SELECT * FROM users WHERE name = '" . $name . "'";
$result = mysqli_query($conn, $query);

Người dùng nhập tài khoản là admin' --. Sau khi nối chuỗi, câu truy vấn trở thành SELECT * FROM users WHERE name = 'admin' --', trong đó -- khiến phần còn lại bị bình luận. Kết quả là truy vấn trả về toàn bộ bảng người dùng mà không cần mật khẩu.

Các biến thể của tấn công gồm: chèn mệnh đề OR để bỏ qua điều kiện lọc, UNION SELECT để đọc dữ liệu từ bảng khác, -- hoặc # để vô hiệu hoá phần đuôi câu lệnh, và stacked query với dấu chấm phẩy để chạy lệnh nguy hiểm tiếp theo.

Các dạng tấn công phổ biến

  • In-band (Error-based): dùng thông báo lỗi để suy ra dữ liệu, nhanh nhưng lộ thông tin ra ngoài.
  • In-band (Boolean-based): chèn điều kiện TRUE/FALSE và quan sát phản hồi, chậm hơn nhưng rất khó phát hiện.
  • Out-of-band: gửi dữ liệu ra ngoài qua kênh phụ như DNS hoặc HTTP, dùng khi không thấy phản hồi trực tiếp.
  • Union-based: chèn UNION để nối kết quả của truy vấn độc hại vào tập kết quả chính.
  • Second-order: dữ liệu độc hại lưu vào cơ sở dữ liệu và chỉ kích hoạt khi truy vấn sau đó dùng tới nó.

Các lỗi từ trường tìm kiếm, tham số URL, header, cookie và JSON body đều có thể là điểm vào. Trong các framework hiện đại, lỗi thường nằm ở chỗ dựng truy vấn thủ công bằng cách nối chuỗi thay vì dùng tham số hoá.

Cách phòng chống hiệu quả

Biện pháp quan trọng nhất là dùng prepared statement (câu lệnh có tham số). Thư viện sẽ tách mã SQL khỏi dữ liệu, nên giá trị người dùng luôn được coi là dữ liệu chứ không phải mã thực thi:

// PDO với emulating bị tắt để thực sự dùng prepared statement
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_EMULATE_PREPARES => false
]);
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$name]);

Ngoài prepared statement, cần kết hợp thêm các lớp phòng thủ khác:

  • Áp dụng allow-list thay vì kiểm tra chuỗi đầu vào bằng biểu thức thông thường, vì regex dễ bị lách.
  • Escape ký tự đặc biệt với hàm chuyên dụng như mysqli_real_escape_string khi buộc phải nối chuỗi.
  • Đặt nguyên tắc quyền tối thiểu cho tài khoản database, cấm quyền DROP hay GRANT.
  • Giấu thông báo lỗi chi tiết khỏi giao diện người dùng, chỉ ghi log phía server.
  • Bổ sung Web Application Firewall để lọc các mẫu tấn công phổ biến ở lớp biên.

Mã hóa cơ sở dữ liệu không phải biện pháp phòng thủ

Một quan niệm sai phổ biến là cứ mã hoá cột mật khẩu thì SQL injection sẽ vô hại. Điều này không đúng: kẻ tấn công vẫn có thể đọc dữ liệu ở dạng bản rõ, vẫn xoá bảng, vẫn thay đổi điều kiện lọc và leo thang đặc quyền. Hash mật khẩu bằng bcrypt hoặc Argon2 là biện pháp đúng, nhưng nó không thay thế cho việc viết truy vấn an toàn.

Ví dụ bị tấn công và cách sửa

Không an toàn An toàn
"SELECT * FROM u WHERE n='$n'" prepare("SELECT * FROM u WHERE n=?")
"SELECT * FROM o WHERE id=" . $_GET['id'] prepare("SELECT * FROM o WHERE id=?"), execute([$id])
kiểm tra bằng if (strpos($s, 'drop')) allow-list các giá trị hợp lệ

Lưu ý thêm rằng prepared statement chỉ bảo vệ phần dữ liệu. Tên bảng, tên cột và từ khoá ORDER BY không thể truyền dưới dạng tham số, nên với các vị trí này cần dùng allow-list ánh xạ từ khóa sang danh sách cột hợp lệ.

Kiểm thử và giám sát

Nên đưa SQL injection vào kiểm thử tự động bằng công cụ như sqlmap ở môi trường thử nghiệm, đồng thời bật WAF ở chế độ chỉ ghi nhận log trước khi chặn thật. Giám sát truy vấn lạ, ví dụ câu lệnh chứa nhiều từ khoá UNION hoặc OR liên tiếp, giúp phát hiện sớm. Bạn có thể tham khảo hướng dẫn SQL injection của OWASP và cheat sheet phòng chống SQL injection.

Kết luận

SQL injection vẫn xuất hiện trong các lỗ hổng top đầu mỗi năm vì nguyên nhân thường chỉ là một dòng nối chuỗi lười biếng. Giải pháp không hề phức tạp: dùng prepared statement ở mọi nơi, kết hợp allow-list và nguyên tắc quyền tối thiểu. Một lần rà soát toàn bộ truy vấn thường chặn được phần lớn rủi ro trước khi cần tới công cụ phát hiện.

Sơ đồ minh hoạ cách mã độc được chèn vào trường nhập và thay đổi câu truy vấn SQL
Ví dụ thực tế các truy vấn SQL injection chèn điều kiện trong mệnh đề WHERE

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

SSH key là gì? Cách đăng nhập máy chủ không cần mật khẩu

SSH key là gì? Đó là một cặp khoá mật mã gồm public key (khoá công khai) và private key (khoá riêng tư), dùng để xác thực bạn là ai…

Xem thêm

nftables là gì? Tường lửa Linux thế hệ mới thay thế iptables

nftables là gì? Tường lửa Linux thế hệ mới thay thế iptables nftables là hệ thống lọc và phân loại gói tin trong nhân Linux, có mặt từ nhân 3.13…

Xem thêm

systemd là gì? Quản lý dịch vụ và tiến trình khởi động trên Linux

systemd là gì? Trình quản lý dịch vụ và tiến trình khởi động trên Linux systemd là gì? systemd là trình quản lý tiến trình và dịch vụ tiêu chuẩn…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất