Tìm hiểu về lỗ hổng SQL Injection và cách khắc phục
SQL Injection là lỗ hổng xảy ra khi ứng dụng ghép trực tiếp dữ liệu người dùng vào câu lệnh SQL, khiến kẻ tấn công thay đổi được ý nghĩa của truy vấn. Cách khắc phục cốt lõi là dùng truy vấn tham số hóa (prepared statement) ở mọi nơi, để cơ sở dữ liệu luôn hiểu dữ liệu là dữ liệu chứ không phải một phần của lệnh. Kết hợp thêm kiểm tra đầu vào, tài khoản cơ sở dữ liệu quyền tối thiểu và không hiển thị lỗi SQL cho người dùng để giảm rủi ro và thiệt hại.
Nguyên nhân gốc của SQL Injection
Hãy hình dung đoạn mã kiểm tra đăng nhập tạo câu truy vấn bằng cách nối chuỗi: lấy tên đăng nhập người dùng nhập vào, đặt giữa hai dấu nháy trong câu SELECT. Nếu người dùng nhập tên bình thường, mọi thứ ổn. Nhưng nếu dữ liệu nhập vào chứa dấu nháy và một đoạn cú pháp SQL, đoạn đó trở thành một phần của câu lệnh. Cơ sở dữ liệu không có cách nào biết phần nào do lập trình viên viết, phần nào do người dùng gửi.
Vấn đề không nằm ở ký tự đặc biệt cụ thể nào, mà ở việc trộn lẫn mã và dữ liệu trong cùng một chuỗi. Đây là cùng một gốc rễ với các lỗi injection khác như chèn lệnh hệ điều hành hay XSS.
Hậu quả có thể xảy ra
- Vượt qua đăng nhập mà không cần mật khẩu.
- Đọc dữ liệu không được phép: thông tin khách hàng, mã băm mật khẩu, đơn hàng.
- Sửa hoặc xóa dữ liệu.
- Trong cấu hình kém, thực thi lệnh trên máy chủ cơ sở dữ liệu hoặc đọc tệp hệ thống.
- Làm chậm hoặc sập cơ sở dữ liệu.
Nhiều vụ rò rỉ dữ liệu lớn trong lịch sử bắt nguồn từ SQL Injection, dù đây là lỗi đã được biết đến và có cách phòng chống rõ ràng từ lâu.
Các dạng SQL Injection
| Dạng | Đặc điểm |
|---|---|
| In-band (dựa trên lỗi, UNION) | Kết quả hoặc thông báo lỗi hiển thị trực tiếp trên trang |
| Blind dựa trên boolean | Không thấy dữ liệu, nhưng trang phản hồi khác nhau khi điều kiện đúng hoặc sai |
| Blind dựa trên thời gian | Suy ra thông tin qua độ trễ phản hồi |
| Out-of-band | Dữ liệu bị gửi ra ngoài qua kênh khác như DNS |
| Second-order | Dữ liệu độc được lưu trước, gây lỗi ở truy vấn khác sau đó |
Dạng second-order đáng chú ý vì lập trình viên thường tin dữ liệu đã nằm trong cơ sở dữ liệu là "sạch". Nguyên tắc đúng: mọi dữ liệu ghép vào truy vấn đều phải tham số hóa, bất kể đến từ đâu.
Khắc phục 1: Truy vấn tham số hóa
Với prepared statement, câu truy vấn được gửi tới cơ sở dữ liệu với các chỗ giữ chỗ (placeholder), dữ liệu được gửi riêng. Cơ sở dữ liệu phân tích cấu trúc câu lệnh trước, sau đó mới gắn dữ liệu vào, nên dữ liệu không bao giờ thay đổi được cấu trúc. Mọi ngôn ngữ và thư viện truy cập cơ sở dữ liệu phổ biến đều hỗ trợ cách này.
Lưu ý: placeholder chỉ dùng cho giá trị. Tên bảng, tên cột, từ khóa sắp xếp (ASC/DESC) không tham số hóa được; với những chỗ này, dùng danh sách cho phép cố định trong mã và ánh xạ từ lựa chọn của người dùng.
Khắc phục 2: Dùng ORM đúng cách
ORM tự sinh truy vấn tham số hóa khi dùng API thông thường. Rủi ro xuất hiện khi lập trình viên dùng các hàm truy vấn thô (raw query) và ghép chuỗi vào đó. Rà soát mọi chỗ dùng truy vấn thô trong dự án.
Khắc phục 3: Stored procedure an toàn
Stored procedure giúp tách logic, nhưng chỉ an toàn khi bên trong không ghép chuỗi để tạo SQL động. Một stored procedure dùng EXEC với chuỗi nối từ tham số vẫn bị injection như thường.
Các lớp bảo vệ bổ sung
- Kiểm tra đầu vào: ID phải là số, email đúng định dạng, độ dài giới hạn. Không thay thế tham số hóa nhưng giảm bề mặt tấn công.
- Quyền tối thiểu: tài khoản ứng dụng chỉ có quyền đọc ghi trên bảng cần thiết; không dùng tài khoản quản trị, không quyền đọc tệp hay thực thi lệnh hệ thống.
- Ẩn thông báo lỗi: lỗi SQL chi tiết chỉ ghi vào log nội bộ.
- Tường lửa ứng dụng web (WAF): chặn mẫu tấn công phổ biến, hữu ích như lớp tạm thời nhưng có thể bị vượt qua.
- Mã hóa dữ liệu nhạy cảm, băm mật khẩu đúng cách: giảm giá trị dữ liệu nếu bị đọc trộm; xem bài cơ chế băm mật khẩu bcrypt.
Quyền tối thiểu không chỉ áp dụng cho tài khoản ứng dụng mà cho cả con người truy cập dữ liệu. Cách tổ chức phân quyền này được bàn trong bài quản lý quyền truy cập dữ liệu nhạy cảm.
Phát hiện SQL Injection trong ứng dụng của bạn
- Tìm trong mã nguồn mọi chỗ tạo truy vấn bằng nối chuỗi hoặc định dạng chuỗi có biến.
- Dùng công cụ phân tích mã tĩnh để tự động đánh dấu mẫu nguy hiểm.
- Thử nhập ký tự đặc biệt vào tham số trên môi trường thử nghiệm và quan sát lỗi 500, phản hồi khác thường.
- Theo dõi log: yêu cầu chứa từ khóa SQL đi kèm lỗi máy chủ; xem bài phân tích log máy chủ.
Chỉ kiểm thử trên ứng dụng bạn sở hữu hoặc được ủy quyền.
Khi phát hiện lỗ hổng đã bị khai thác
- Vá lỗi ngay bằng tham số hóa, không chỉ chặn chuỗi tấn công cụ thể.
- Xác định dữ liệu nào có thể đã bị truy cập qua log ứng dụng và log cơ sở dữ liệu.
- Đổi mật khẩu tài khoản cơ sở dữ liệu, thu hồi khóa có thể đã lộ.
- Nếu mã băm mật khẩu người dùng có thể đã lộ, yêu cầu đặt lại mật khẩu.
- Thực hiện nghĩa vụ thông báo theo quy định về bảo vệ dữ liệu cá nhân nếu áp dụng.
SQL Injection là ví dụ kinh điển cho nhóm Injection trong OWASP Top 10, và bài học từ nó áp dụng cho mọi nơi dữ liệu gặp trình thông dịch.
Câu hỏi thường gặp
Dùng hàm escape chuỗi có đủ chống SQL Injection không?
Escape thủ công dễ sai, phụ thuộc bộ ký tự và ngữ cảnh, và không áp dụng được cho giá trị số không đặt trong dấu nháy. Truy vấn tham số hóa là cách được khuyến nghị.
Cơ sở dữ liệu NoSQL có bị injection không?
Có. NoSQL không dùng cú pháp SQL nhưng vẫn có thể bị chèn toán tử truy vấn nếu ứng dụng đưa đối tượng do người dùng gửi thẳng vào truy vấn. Nguyên tắc vẫn là kiểm tra kiểu dữ liệu và không trộn dữ liệu với cấu trúc truy vấn.
WAF có thay thế được việc sửa mã không?
Không. WAF là lớp bảo vệ bổ sung, có thể chặn tấn công phổ biến trong lúc chờ vá, nhưng kẻ tấn công có nhiều kỹ thuật vượt qua. Sửa tận gốc trong mã mới là giải pháp lâu dài.
ORM có hoàn toàn miễn nhiễm SQL Injection?
Không hoàn toàn. ORM an toàn khi dùng API chuẩn, nhưng các hàm truy vấn thô, sắp xếp theo tên cột do người dùng gửi hoặc một số tính năng nâng cao vẫn có thể bị injection nếu dùng sai.
Bài liên quan

Bảo mật API trong môi trường phát triển ứng dụng Web
Các nguyên tắc bảo mật API cho lập trình viên web: xác thực, phân quyền theo đối tượng, kiểm tra đầu vào, giới hạn tần suất, quản lý khóa, nhật ký và kiểm thử.

Bảo mật tệp tin cấu hình nhạy cảm tránh bị lộ lọt
Cách bảo vệ tệp .env, config, khóa API, mật khẩu cơ sở dữ liệu: không đưa vào Git, chặn truy cập web, phân quyền tệp, dùng trình quản lý bí mật và xử lý khi lộ.

Các bước cơ bản để rà soát lỗ hổng bảo mật ứng dụng
Quy trình rà soát lỗ hổng ứng dụng web: xác định phạm vi, lập danh mục tài sản, quét tự động, rà soát mã, kiểm thử thủ công và theo dõi khắc phục.

Các chương trình tìm lỗi nhận thưởng Bug Bounty và lưu ý
Bug Bounty hoạt động thế nào, các dạng chương trình, quy tắc phạm vi và safe harbor, cách viết báo cáo tốt và lưu ý pháp lý cho người mới.