- 1 Ransomware là gì? Mã độc mã hóa tống tiền và cách phòng chống
- 2 SQL Injection là gì? Tấn công cơ sở dữ liệu và cách phòng chống
- 3 Tường lửa là gì? Vai trò và chức năng trong bảo mật mạng
- 4 MFA Là Gì? So Sánh MFA vs 2FA và Các Phương Thức Xác Thực
- 5 VPN là gì? Mạng riêng ảo, WireGuard và OpenVPN
- 6 Zero Trust Là Gì? Mô Hình Bảo Mật 'Không Tin Tưởng Ai'
- 7 Trojan là gì? Những thông tin cơ bản về mã độc Trojan
- 8 OAuth 2.0 là gì? Ủy quyền truy cập và đăng nhập bằng Google/Facebook
- 9 DNS Sinkhole là gì? Ứng dụng và cách dùng kỹ thuật DNS Sinkhole
- 10 Phishing là gì? Nhận diện và phòng chống tấn công lừa đảo trực tuyến
- 11 DDos là gì? Dấu hiệu, xử lý và cách phòng chống hiệu quả
- 12 Mã độc là gì? Phân loại, đặc tính và cách phòng tránh
SQL Injection là lỗ hổng bảo mật web nguy hiểm nhất, tồn tại hơn 25 năm nhưng vẫn đứng đầu danh sách OWASP Top 10 năm 2021. Chỉ một dấu nháy đơn (') được nhập vào ô tìm kiếm hay form đăng nhập có thể khiến attacker truy cập toàn bộ database của ứng dụng bạn — không cần mật khẩu, không cần tài khoản đặc biệt.
Cần giải pháp dữ liệu cho doanh nghiệp?
AlgoData đồng hành cùng doanh nghiệp về data engineering, phân tích & AI từ 2019.
SQL Injection là gì? OWASP A03:2021
SQL Injection (SQLi) là kỹ thuật tấn công mà attacker chèn (inject) câu lệnh SQL độc hại vào các tham số đầu vào của ứng dụng web. Khi ứng dụng không xử lý đúng cách, database engine sẽ thực thi các câu lệnh này như thể chúng là một phần hợp lệ của query — dẫn đến rò rỉ dữ liệu, bypass xác thực hoặc phá hủy dữ liệu.
Trong danh sách OWASP Top 10 Web Application Security Risks 2021, SQL Injection xếp trong nhóm A03: Injection — một trong những rủi ro bảo mật phổ biến và nghiêm trọng nhất. Injection nói chung (bao gồm SQLi, LDAP injection, OS command injection) được ghi nhận trong 94% ứng dụng được kiểm tra, với tỷ lệ xuất hiện là 19% theo dữ liệu của OWASP.
Tại sao SQLi vẫn phổ biến sau hơn 25 năm?
SQL Injection được ghi nhận lần đầu vào cuối thập niên 1990. Sau hơn 25 năm, câu hỏi tự nhiên là: tại sao lỗ hổng này vẫn chưa bị loại trừ hoàn toàn?
Nguyên nhân kỹ thuật: Nhiều ứng dụng — đặc biệt là các hệ thống legacy — vẫn ghép chuỗi trực tiếp (string concatenation) khi xây dựng SQL query. Đây là cách viết tự nhiên với developer mới bắt đầu, và nếu không có code review hoặc SAST (Static Application Security Testing), nó dễ dàng lọt qua.
Nguyên nhân con người: Áp lực deadline khiến nhiều team bỏ qua các best practice. Ngoài ra, nhiều codebase kế thừa được viết trước khi Prepared Statement trở thành tiêu chuẩn ngành.
Bề mặt tấn công rộng: Bất kỳ điểm nào ứng dụng nhận dữ liệu từ người dùng (form, URL parameter, cookie, HTTP header) và đưa vào SQL query đều là điểm tiềm năng bị tấn công.

Cơ chế tấn công: từ input đến database
Để hiểu SQLi, cần hiểu luồng dữ liệu từ khi người dùng nhập liệu đến khi database thực thi query.
Luồng dữ liệu trong ứng dụng dễ bị tấn công
- Người dùng nhập dữ liệu vào form (username, password, search query...)
- Ứng dụng ghép chuỗi input trực tiếp vào SQL string
- SQL string được gửi đến database engine
- Database thực thi toàn bộ SQL — bao gồm cả phần attacker chèn vào
Đây là ví dụ kinh điển về code PHP dễ bị tấn công:
1// VULNERABLE — đừng làm thế này
2$username = $_POST['username'];
3$password = $_POST['password'];
4$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
Trong trường hợp bình thường, nếu người dùng nhập alice và secret123, query sẽ là:
1SELECT * FROM users WHERE username='alice' AND password='secret123'
Đây là query hợp lệ và vô hại. Nhưng điều gì xảy ra khi attacker nhập admin'-- vào trường username?
Payload tấn công và tác động
Khi attacker nhập admin'-- làm username (và bất cứ thứ gì làm password):
1-- Query bị inject thành:
2SELECT * FROM users WHERE username='admin'--' AND password='anything'
3-- Phần sau -- bị comment out → bypass password check
Dấu -- trong SQL là ký hiệu comment (trên MySQL có thể dùng #). Toàn bộ phần AND password='anything' bị bỏ qua. Database chỉ kiểm tra username, và nếu tài khoản admin tồn tại, attacker đăng nhập thành công mà không cần biết mật khẩu.
Hậu quả của một lần tấn công thành công
Tùy vào cấu hình và quyền hạn của database user mà ứng dụng sử dụng, attacker có thể:
- Đọc dữ liệu nhạy cảm: toàn bộ bảng users, thông tin thẻ tín dụng, dữ liệu y tế
- Bypass xác thực: đăng nhập với bất kỳ tài khoản nào, kể cả admin
- Sửa/xóa dữ liệu: UPDATE hoặc DELETE không giới hạn
- Dump toàn bộ schema: biết cấu trúc database để lên kế hoạch tấn công tiếp theo
- Thực thi lệnh hệ điều hành: trên MySQL với
INTO OUTFILE, trên SQL Server vớixp_cmdshell
Các loại SQL Injection
Không phải mọi SQLi đều hoạt động theo cùng một cơ chế. Attacker sử dụng nhiều biến thể khác nhau tùy theo cách ứng dụng xử lý và hiển thị kết quả.
Classic (In-band) SQLi
Đây là dạng phổ biến và trực tiếp nhất. Kết quả của câu lệnh SQL độc hại được trả về trực tiếp trong response của ứng dụng — cùng kênh với request ban đầu.
Error-based SQLi: Ứng dụng hiển thị thông báo lỗi chi tiết của database. Attacker khai thác thông tin trong thông báo lỗi để biết cấu trúc database, phiên bản, tên bảng.
UNION-based SQLi: Attacker sử dụng toán tử UNION để ghép kết quả từ một SELECT khác vào kết quả gốc. Đây là cách phổ biến nhất để dump dữ liệu từ các bảng khác.
Blind Boolean-based SQLi
Trong trường hợp ứng dụng không hiển thị kết quả query hay thông báo lỗi trực tiếp, attacker vẫn có thể khai thác bằng cách quan sát sự khác biệt trong response khi điều kiện là TRUE hay FALSE.
Ví dụ: attacker thêm điều kiện AND 1=1 (TRUE) vs AND 1=2 (FALSE) và quan sát xem trang có hiển thị kết quả bình thường hay trống. Từ đó, từng bit thông tin có thể được suy ra bằng cách hỏi các câu hỏi có/không.
Quá trình này chậm nhưng hoàn toàn tự động được bằng các công cụ như sqlmap.
Blind Time-based SQLi
Tương tự Boolean-based, nhưng thay vì quan sát nội dung response, attacker đo thời gian server trả về response. Bằng cách sử dụng hàm SLEEP() (MySQL), WAITFOR DELAY (SQL Server), hay pg_sleep() (PostgreSQL), attacker có thể suy ra thông tin dựa trên độ trễ.
Ví dụ: IF(1=1, SLEEP(5), 0) — nếu server mất 5 giây để trả lời, điều kiện là TRUE.
Out-of-band SQLi
Loại tấn công hiếm gặp nhất, phụ thuộc vào khả năng của database server trong việc tạo ra các kết nối mạng ra ngoài (DNS query, HTTP request). Attacker không cần đọc response trực tiếp; thay vào đó, dữ liệu được gửi ra ngoài qua một kênh khác (ví dụ DNS lookup đến domain do attacker kiểm soát).

Payload mẫu (educational)
Lưu ý quan trọng: Các payload dưới đây được cung cấp chỉ với mục đích giáo dục và hiểu biết về bảo mật. Sử dụng các kỹ thuật này để tấn công hệ thống mà bạn không có quyền là hành vi vi phạm pháp luật. Chỉ áp dụng trên môi trường test/lab do bạn kiểm soát.
1-- Login bypass
2' OR '1'='1
3' OR 1=1--
4admin'--
5
6-- Data dump (UNION-based)
7' UNION SELECT username, password, NULL FROM users--
8
9-- Database version
10' UNION SELECT @@version, NULL, NULL--
11
12-- Time-based blind (MySQL)
13'; SELECT SLEEP(5)--
14
15-- Drop table (destructive)
16'; DROP TABLE users--
Giải thích từng payload
' OR '1'='1 — Đóng chuỗi string hiện tại bằng dấu nháy đơn, thêm điều kiện luôn đúng. Kết quả: WHERE clause luôn TRUE → trả về tất cả rows.
admin'-- — Đóng chuỗi sau username, dùng -- để comment out phần còn lại của query (thường là kiểm tra password). Kết quả: bypass xác thực mật khẩu.
UNION SELECT — Nối thêm một SELECT vào kết quả gốc để đọc dữ liệu từ bảng khác (phải khớp số cột và kiểu dữ liệu).
SLEEP(5) — Dùng cho time-based blind SQLi: đo độ trễ để xác nhận lỗ hổng tồn tại.
DROP TABLE — Tấn công phá hủy dữ liệu. Trong thực tế, nhiều DB user không có quyền DROP — đây là lý do tại sao principle of least privilege quan trọng.
Code phòng chống: Prepared Statement
Prepared Statement (còn gọi là Parameterized Query) là phương pháp phòng chống SQL Injection hiệu quả và đáng tin cậy nhất. Thay vì ghép chuỗi input vào SQL, Prepared Statement tách biệt hoàn toàn cấu trúc SQL và dữ liệu.
Cơ chế hoạt động:
- Ứng dụng gửi SQL template (với placeholder
?hoặc:name) đến database - Database compile và parse SQL template — lúc này cấu trúc query đã được xác định
- Ứng dụng gửi dữ liệu thực tế riêng biệt
- Database thực thi query với dữ liệu đã được bind vào placeholder — không có cách nào dữ liệu có thể thay đổi cấu trúc query
Đây là cách triển khai đúng trong các ngôn ngữ phổ biến:
1// PHP PDO — SAFE
2$pdo = new PDO($dsn, $user, $pass);
3$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
4$stmt->execute([$username, $password_hash]);
5$user = $stmt->fetch();
1# Python psycopg2 — SAFE
2import psycopg2
3conn = psycopg2.connect(dsn)
4cur = conn.cursor()
5cur.execute(
6 "SELECT * FROM users WHERE username = %s AND password = %s",
7 (username, password_hash)
8)
9user = cur.fetchone()
1// Java JDBC — SAFE
2String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
3PreparedStatement stmt = conn.prepareStatement(sql);
4stmt.setString(1, username);
5stmt.setString(2, passwordHash);
6ResultSet rs = stmt.executeQuery();
Tại sao Prepared Statement an toàn?
Khi attacker nhập admin'-- vào trường username với Prepared Statement:
- Database đã biết cấu trúc query:
WHERE username = ? AND password = ? - Giá trị
admin'--được truyền vào như một string literal, không phải SQL code - Database xử lý nó như một chuỗi ký tự thông thường — kể cả dấu
'và-- - Không có injection nào xảy ra
ORM và SQLAlchemy — an toàn hay không?
ORM (Object-Relational Mapping) như SQLAlchemy, Hibernate, ActiveRecord... cung cấp lớp abstraction trên SQL. Câu hỏi thường gặp: "Tôi dùng ORM rồi, có cần lo về SQLi không?"
Câu trả lời: Có, vẫn cần lo — nếu bạn dùng raw SQL trong ORM.
1# SQLAlchemy — SAFE (ORM query)
2user = session.query(User).filter(User.username == username).first()
3
4# SQLAlchemy — VULNERABLE (raw f-string)
5result = session.execute(f"SELECT * FROM users WHERE username = '{username}'")
6
7# SQLAlchemy — SAFE (raw SQL với bindparam)
8from sqlalchemy import text
9result = session.execute(
10 text("SELECT * FROM users WHERE username = :username"),
11 {"username": username}
12)
Phân tích
ORM query builder (SAFE): Khi bạn dùng .filter(User.username == username), SQLAlchemy tự động tạo parameterized query phía sau. Không có string concatenation nào xảy ra.
Raw f-string (VULNERABLE): Đây là lỗi phổ biến nhất khi developer muốn viết SQL tùy chỉnh. F-string ghép trực tiếp username vào SQL string — đây chính xác là pattern bị SQLi exploit.
text() với named parameters (SAFE): Khi cần raw SQL, luôn dùng text() của SQLAlchemy với :param_name placeholder và truyền giá trị qua dictionary. SQLAlchemy sẽ tự động sử dụng parameterized query.
Lưu ý với Stored Procedures
Stored Procedure không tự động an toàn trước SQLi. Nếu stored procedure bên trong dùng dynamic SQL (EXEC, sp_executesql) với string concatenation, nó vẫn bị vulnerable. Luôn kiểm tra code bên trong stored procedure.
WAF, Input validation và tại sao không đủ
Web Application Firewall (WAF)
WAF hoạt động bằng cách phân tích request HTTP và chặn những request chứa pattern SQLi đã biết (dấu nháy đơn, keywords như UNION, SELECT, DROP...). Nghe có vẻ hiệu quả, nhưng thực tế có nhiều cách bypass:
Encoding bypass: %27 là URL-encoding của '. Nhiều WAF không decode đúng cách trước khi kiểm tra.
Comment insertion: UN/**/ION — comment SQL trong giữa keyword. MySQL và một số DB khác chấp nhận cú pháp này.
Case variation: SeLeCt, uNiOn, sElEcT — WAF dùng case-sensitive matching sẽ bị qua mặt.
Double encoding: %2527 → decode thành %27 → decode thành '.
Whitespace alternatives: Tab, newline, carriage return có thể thay thế space trong SQL.
Input validation — con dao hai lưỡi
Một số team cố gắng ngăn SQLi bằng cách strip hoặc escape các ký tự đặc biệt như ', ", ;. Vấn đề:
- Breaks legitimate data: Tên người dùng như
O'Brien,D'Souzahoàn toàn hợp lệ nhưng sẽ bị chặn. - Không đủ toàn diện: Có hàng chục cách để encode và obfuscate SQLi payload.
- False sense of security: Developer nghĩ đã an toàn trong khi thực tế vẫn còn lỗ hổng.
Defense-in-depth: chiến lược đúng đắn
Phòng chống SQLi hiệu quả cần nhiều lớp bảo vệ:
-
Prepared Statement (bắt buộc): Đây là biện pháp căn bản và không thể thay thế. Không có lớp bảo vệ nào khác đủ hiệu quả nếu không có Prepared Statement.
-
Least Privilege DB User: Tài khoản database mà ứng dụng sử dụng chỉ nên có quyền tối thiểu cần thiết (SELECT, INSERT, UPDATE trên các bảng cụ thể). Không bao giờ dùng root/sa/admin account cho ứng dụng.
-
WAF (bổ sung): Phát hiện và chặn các tấn công đã biết, giảm noise trong log. Nhưng không phải giải pháp chính.
-
SAST/DAST: Static Application Security Testing để phát hiện vulnerable code pattern trong quá trình phát triển.
-
Error handling đúng cách: Không hiển thị stack trace hay thông báo lỗi database chi tiết cho người dùng. Log lỗi phía server, trả về thông báo generic cho client.
-
Monitoring và alerting: Phát hiện pattern bất thường (nhiều lần thử với input chứa
', response time bất thường) để phản ứng kịp thời.

OWASP reference và CVE nổi tiếng
OWASP SQL Injection Prevention Cheat Sheet
OWASP cung cấp hướng dẫn chi tiết về phòng chống SQL Injection tại OWASP SQL Injection Prevention Cheat Sheet. Tài liệu này bao gồm:
- Danh sách các Prepared Statement API cho từng ngôn ngữ
- Stored Procedures — khi nào an toàn, khi nào không
- Escaping — chỉ dùng như biện pháp cuối cùng khi không thể dùng Prepared Statement
- Input validation — cách thực hiện đúng
- Least Privilege recommendations
CVE nổi tiếng liên quan đến SQL Injection
CVE-2011-4505 — Joomla SQL Injection: Lỗ hổng SQLi trong Joomla CMS ảnh hưởng đến hơn 1,5 triệu website trên toàn thế giới. Attacker có thể thực hiện unauthenticated SQL Injection thông qua tham số trong URL, cho phép đọc toàn bộ database bao gồm thông tin đăng nhập của admin. Đây là một trong những CVE có tầm ảnh hưởng rộng nhất trong lịch sử CMS.
Yahoo! Data Breach 2012 — SQL Injection: Năm 2012, nhóm hacker D33Ds Company công bố đã đánh cắp 450,000 thông tin đăng nhập (username và password dạng plaintext) từ Yahoo! Voices (trước đây là Associated Content) thông qua SQL Injection. Vụ rò rỉ này không chỉ ảnh hưởng đến Yahoo! mà còn đến các dịch vụ khác vì nhiều người dùng dùng cùng một mật khẩu cho nhiều tài khoản.
Bài học từ các vụ tấn công lịch sử:
- SQLi không chỉ tấn công vào ứng dụng nhỏ — các công ty lớn, nền tảng phổ biến cũng có thể bị.
- Một lỗ hổng SQLi trong thư viện/CMS có thể ảnh hưởng đến hàng triệu website chạy nền tảng đó.
- Dữ liệu bị đánh cắp thường được bán hoặc công bố công khai — thiệt hại về danh tiếng thường lớn hơn thiệt hại tài chính trực tiếp.
Tóm tắt: checklist phòng chống SQLi
Trước khi deploy bất kỳ tính năng nào có tương tác với database, hãy kiểm tra:
- Tất cả SQL query đều dùng Prepared Statement / Parameterized Query
- Không có f-string/string concatenation nào trong SQL code
- ORM raw queries (nếu có) dùng bindparam, không phải f-string
- Database user chỉ có quyền tối thiểu cần thiết
- Error messages không lộ thông tin database/stack trace
- WAF được cấu hình và đang hoạt động (defense-in-depth)
- Code đã qua SAST scan để phát hiện injection pattern
SQL Injection có thể phòng chống hoàn toàn. Không giống như nhiều lỗ hổng bảo mật phức tạp khác, giải pháp cho SQLi đơn giản và rõ ràng: luôn dùng Prepared Statement, không bao giờ ghép chuỗi SQL từ user input. Một thói quen code đúng ngay từ đầu sẽ loại bỏ hoàn toàn rủi ro này.

