SQL Injection là gì? Tấn công cơ sở dữ liệu và cách phòng chống
Security

SQL Injection là gì? Tấn công cơ sở dữ liệu và cách phòng chống

SQL Injection (SQLi) là lỗ hổng OWASP A03:2021 cho phép attacker chèn câu lệnh SQL độc hại vào query của ứng dụng, truy cập hoặc xóa toàn bộ database. Tìm hiểu cơ chế tấn công, payload mẫu và cách phòng chống bằng Prepared Statement.

Trong series: Bảo mật
  1. 1 Ransomware là gì? Mã độc mã hóa tống tiền và cách phòng chống
  2. 2 SQL Injection là gì? Tấn công cơ sở dữ liệu và cách phòng chống
  3. 3 Tường lửa là gì? Vai trò và chức năng trong bảo mật mạng
  4. 4 MFA Là Gì? So Sánh MFA vs 2FA và Các Phương Thức Xác Thực
  5. 5 VPN là gì? Mạng riêng ảo, WireGuard và OpenVPN
  6. 6 Zero Trust Là Gì? Mô Hình Bảo Mật 'Không Tin Tưởng Ai'
  7. 7 Trojan là gì? Những thông tin cơ bản về mã độc Trojan
  8. 8 OAuth 2.0 là gì? Ủy quyền truy cập và đăng nhập bằng Google/Facebook
  9. 9 DNS Sinkhole là gì? Ứng dụng và cách dùng kỹ thuật DNS Sinkhole
  10. 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. 11 DDos là gì? Dấu hiệu, xử lý và cách phòng chống hiệu quả
  12. 12 Mã độc là gì? Phân loại, đặc tính và cách phòng tránh
✦ Tóm tắt nhanh
SQL Injection (SQLi) là lỗ hổng OWASP A03:2021 cho phép attacker chèn câu lệnh SQL độc hại vào query của ứng dụng, truy cập hoặc xóa toàn bộ database. Tìm hiểu cơ chế tấn công, payload mẫu và cách phò...
Bài này thế nào?

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.

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

  1. Người dùng nhập dữ liệu vào form (username, password, search query...)
  2. Ứng dụng ghép chuỗi input trực tiếp vào SQL string
  3. SQL string được gửi đến database engine
  4. 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:

php
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 alicesecret123, query sẽ là:

SQL
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):

SQL
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ới xp_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.

SQL
 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 SQLdữ liệu.

Cơ chế hoạt động:

  1. Ứng dụng gửi SQL template (với placeholder ? hoặc :name) đến database
  2. Database compileparse SQL template — lúc này cấu trúc query đã được xác định
  3. Ứng dụng gửi dữ liệu thực tế riêng biệt
  4. 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:

php
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();
Python
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()
Java
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 '--
  • 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.

Python
 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'Souza hoà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ệ:

  1. 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.

  2. 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.

  3. 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.

  4. SAST/DAST: Static Application Security Testing để phát hiện vulnerable code pattern trong quá trình phát triển.

  5. 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.

  6. 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

XSS là gì? Cross-Site Scripting và cách phòng chống

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.

SQL Injection is the most dangerous web security vulnerability, existing for over 25 years yet still ranking at the top of the OWASP Top 10 2021 list. A single apostrophe (') entered into a search box or login form can allow an attacker to access your entire database — no password needed, no special account required.

What is SQL Injection? OWASP A03:2021

SQL Injection (SQLi) is an attack technique where an attacker injects malicious SQL statements into the input parameters of a web application. When the application does not handle this correctly, the database engine executes these statements as if they were a legitimate part of the query — leading to data leakage, authentication bypass, or data destruction.

In the OWASP Top 10 Web Application Security Risks 2021, SQL Injection falls under A03: Injection — one of the most prevalent and serious security risks. Injection in general (including SQLi, LDAP injection, OS command injection) was found in 94% of applications tested, with an incidence rate of 19% according to OWASP data.

Why is SQLi still prevalent after 25+ years?

SQL Injection was first documented in the late 1990s. After more than 25 years, the natural question is: why hasn't this vulnerability been completely eliminated?

Technical reasons: Many applications — especially legacy systems — still use direct string concatenation when building SQL queries. This is a natural way of writing code for beginners, and without code review or SAST (Static Application Security Testing), it easily slips through.

Human factors: Deadline pressure leads many teams to skip best practices. Additionally, many inherited codebases were written before Prepared Statements became the industry standard.

Wide attack surface: Any point where the application receives data from users (forms, URL parameters, cookies, HTTP headers) and passes it into a SQL query is a potential attack point.

The Attack Mechanism: From Input to Database

To understand SQLi, you need to understand the data flow from when a user enters input to when the database executes the query.

Data flow in a vulnerable application

  1. User enters data into a form (username, password, search query...)
  2. The application concatenates the input directly into a SQL string
  3. The SQL string is sent to the database engine
  4. The database executes all the SQL — including the part the attacker injected

Here is a classic example of vulnerable PHP code:

php
1// VULNERABLE — don't do this
2$username = $_POST['username'];
3$password = $_POST['password'];
4$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";

In a normal case, if the user enters alice and secret123, the query will be:

SQL
1SELECT * FROM users WHERE username='alice' AND password='secret123'

This is a valid and harmless query. But what happens when an attacker enters admin'-- in the username field?

Attack payload and impact

When an attacker enters admin'-- as the username (and anything as the password):

SQL
1-- Query becomes injected as:
2SELECT * FROM users WHERE username='admin'--' AND password='anything'
3-- Everything after -- is commented out → password check bypassed

-- in SQL is the comment symbol (on MySQL you can also use #). The entire AND password='anything' portion is ignored. The database only checks the username, and if the admin account exists, the attacker logs in successfully without knowing the password.

Consequences of a successful attack

Depending on the configuration and privileges of the database user the application uses, an attacker can:

  • Read sensitive data: entire users table, credit card information, medical records
  • Bypass authentication: log in as any account, including admin
  • Modify/delete data: unlimited UPDATE or DELETE
  • Dump the entire schema: learn the database structure to plan further attacks
  • Execute operating system commands: on MySQL with INTO OUTFILE, on SQL Server with xp_cmdshell

Types of SQL Injection

Not all SQLi works by the same mechanism. Attackers use different variants depending on how the application processes and displays results.

Classic (In-band) SQLi

This is the most common and direct form. The result of the malicious SQL statement is returned directly in the application response — the same channel as the original request.

Error-based SQLi: The application displays detailed database error messages. The attacker exploits information in error messages to learn the database structure, version, and table names.

UNION-based SQLi: The attacker uses the UNION operator to append results from another SELECT to the original results. This is the most common way to dump data from other tables.

Blind Boolean-based SQLi

When the application does not display query results or error messages directly, an attacker can still exploit it by observing differences in response when a condition is TRUE vs FALSE.

For example: the attacker adds condition AND 1=1 (TRUE) vs AND 1=2 (FALSE) and observes whether the page displays normal results or is empty. From there, information can be inferred bit by bit by asking yes/no questions.

This process is slow but fully automatable with tools like sqlmap.

Blind Time-based SQLi

Similar to Boolean-based, but instead of observing response content, the attacker measures time for the server to return a response. By using SLEEP() (MySQL), WAITFOR DELAY (SQL Server), or pg_sleep() (PostgreSQL) functions, the attacker can infer information based on delays.

For example: IF(1=1, SLEEP(5), 0) — if the server takes 5 seconds to respond, the condition is TRUE.

Out-of-band SQLi

The rarest attack type, depending on the database server's ability to create outbound network connections (DNS queries, HTTP requests). The attacker doesn't need to read the response directly; instead, data is sent out via another channel (e.g., DNS lookup to an attacker-controlled domain).

Example Payloads (Educational)

Important note: The payloads below are provided for educational purposes and security awareness only. Using these techniques to attack systems you do not have permission to access is illegal. Only apply these in test/lab environments that you control.

SQL
 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--

Explanation of each payload

' OR '1'='1 — Closes the current string with an apostrophe, adds a condition that is always true. Result: the WHERE clause is always TRUE → returns all rows.

admin'-- — Closes the string after the username, uses -- to comment out the rest of the query (usually the password check). Result: password authentication bypass.

UNION SELECT — Appends another SELECT to the original results to read data from another table (must match number of columns and data types).

SLEEP(5) — Used for time-based blind SQLi: measures delay to confirm the vulnerability exists.

DROP TABLE — Destructive data attack. In practice, many DB users don't have DROP permission — this is why the principle of least privilege is important.

Prevention Code: Prepared Statements

Prepared Statements (also called Parameterized Queries) are the most effective and reliable method for preventing SQL Injection. Instead of concatenating input into SQL, Prepared Statements completely separate the SQL structure from the data.

How it works:

  1. The application sends an SQL template (with ? or :name placeholders) to the database
  2. The database compiles and parses the SQL template — at this point, the query structure is fixed
  3. The application sends the actual data separately
  4. The database executes the query with data bound to placeholders — there is no way for data to change the query structure

Here is the correct implementation in popular languages:

php
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();
Python
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()
Java
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();

Why are Prepared Statements safe?

When an attacker enters admin'-- in the username field with a Prepared Statement:

  • The database already knows the query structure: WHERE username = ? AND password = ?
  • The value admin'-- is passed in as a string literal, not SQL code
  • The database treats it as a plain string of characters — including the ' and --
  • No injection occurs

ORM and SQLAlchemy — Safe or Not?

ORMs (Object-Relational Mappers) like SQLAlchemy, Hibernate, ActiveRecord... provide an abstraction layer over SQL. A common question: "I'm using an ORM, do I need to worry about SQLi?"

The answer: Yes, you still need to worry — if you use raw SQL in the ORM.

Python
 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 with bindparam)
 8from sqlalchemy import text
 9result = session.execute(
10    text("SELECT * FROM users WHERE username = :username"),
11    {"username": username}
12)

Analysis

ORM query builder (SAFE): When you use .filter(User.username == username), SQLAlchemy automatically creates a parameterized query behind the scenes. No string concatenation occurs.

Raw f-string (VULNERABLE): This is the most common mistake when a developer wants to write custom SQL. The f-string directly concatenates the username into the SQL string — this is exactly the pattern exploited by SQLi.

text() with named parameters (SAFE): When you need raw SQL, always use SQLAlchemy's text() with :param_name placeholders and pass values via a dictionary. SQLAlchemy will automatically use a parameterized query.

Note on Stored Procedures

Stored Procedures are not automatically safe against SQLi. If the stored procedure internally uses dynamic SQL (EXEC, sp_executesql) with string concatenation, it is still vulnerable. Always check the code inside stored procedures.

WAF, Input Validation, and Why They're Not Enough

Web Application Firewall (WAF)

A WAF works by analyzing HTTP requests and blocking those containing known SQLi patterns (apostrophes, keywords like UNION, SELECT, DROP...). This sounds effective, but there are many bypass techniques:

Encoding bypass: %27 is the URL-encoding of '. Many WAFs don't decode properly before checking.

Comment insertion: UN/**/ION — SQL comments within a keyword. MySQL and some other databases accept this syntax.

Case variation: SeLeCt, uNiOn, sElEcT — WAFs using case-sensitive matching are fooled.

Double encoding: %2527 → decoded to %27 → decoded to '.

Whitespace alternatives: Tab, newline, carriage return can replace spaces in SQL.

Input validation — a double-edged sword

Some teams try to prevent SQLi by stripping or escaping special characters like ', ", ;. The problems:

  • Breaks legitimate data: Usernames like O'Brien, D'Souza are completely valid but will be blocked.
  • Not comprehensive enough: There are dozens of ways to encode and obfuscate SQLi payloads.
  • False sense of security: Developers think they're safe when vulnerabilities still exist.

Defense-in-depth: the right strategy

Effective SQLi prevention requires multiple layers of protection:

  1. Prepared Statements (mandatory): This is the fundamental and irreplaceable measure. No other protection layer is sufficient without Prepared Statements.

  2. Least Privilege DB User: The database account used by the application should only have the minimum necessary permissions (SELECT, INSERT, UPDATE on specific tables). Never use root/sa/admin accounts for the application.

  3. WAF (supplemental): Detects and blocks known attacks, reduces noise in logs. But not the primary solution.

  4. SAST/DAST: Static Application Security Testing to detect vulnerable code patterns during development.

  5. Proper error handling: Don't display stack traces or detailed database error messages to users. Log errors server-side, return generic messages to the client.

  6. Monitoring and alerting: Detect unusual patterns (many attempts with input containing ', unusual response times) for timely response.

OWASP Reference and Notable CVEs

OWASP SQL Injection Prevention Cheat Sheet

OWASP provides detailed guidance on SQL Injection prevention at the OWASP SQL Injection Prevention Cheat Sheet. This document covers:

  • List of Prepared Statement APIs for each language
  • Stored Procedures — when they're safe, when they're not
  • Escaping — only as a last resort when Prepared Statements aren't possible
  • Input validation — how to do it correctly
  • Least Privilege recommendations

CVE-2011-4505 — Joomla SQL Injection: An SQLi vulnerability in Joomla CMS affected more than 1.5 million websites worldwide. Attackers could perform unauthenticated SQL Injection through URL parameters, allowing them to read the entire database including admin login credentials. This is one of the most broadly impactful CVEs in CMS history.

Yahoo! Data Breach 2012 — SQL Injection: In 2012, the hacker group D33Ds Company announced they had stolen 450,000 login credentials (usernames and plaintext passwords) from Yahoo! Voices (formerly Associated Content) via SQL Injection. This breach affected not just Yahoo! but also other services because many users reused the same passwords across multiple accounts.

Lessons from historical attacks:

  • SQLi doesn't only target small applications — large companies and popular platforms can also be affected.
  • A single SQLi vulnerability in a library/CMS can impact millions of websites running that platform.
  • Stolen data is often sold or publicly disclosed — reputational damage is usually greater than direct financial damage.

Summary: SQLi Prevention Checklist

Before deploying any feature that interacts with a database, check:

  • All SQL queries use Prepared Statements / Parameterized Queries
  • No f-strings/string concatenation in SQL code
  • ORM raw queries (if any) use bindparam, not f-strings
  • Database user has only the minimum necessary permissions
  • Error messages don't expose database information/stack traces
  • WAF is configured and operational (defense-in-depth)
  • Code has been SAST-scanned to detect injection patterns

SQL Injection is completely preventable. Unlike many other complex security vulnerabilities, the solution for SQLi is simple and clear: always use Prepared Statements, never concatenate SQL strings from user input. One correct coding habit from the start will completely eliminate this risk.

What is XSS? Cross-Site Scripting and Prevention

What is a Firewall?