什么是SQL注入?数据库攻击与防护
Security

什么是SQL注入?数据库攻击与防护

SQL注入(SQLi)是OWASP A03:2021漏洞,允许攻击者将恶意SQL语句注入应用程序查询,从而访问或删除整个数据库。了解攻击机制、示例载荷及使用预处理语句的防护方法。

系列文章: Bảo mật
  1. 1 什么是恶意软件?分类、特征及预防方法
  2. 2 什么是DDoS?识别迹象、应对方法与有效防御指南
  3. 3 什么是网络钓鱼?识别与防范在线欺诈
  4. 4 什么是DNS Sinkhole?DNS Sinkhole技术的应用与使用方法
  5. 5 什么是OAuth 2.0?授权访问与谷歌登录原理
  6. 6 什么是木马病毒?关于Trojan恶意软件的基本知识
  7. 7 Zero Trust 是什么?'永不信任,始终验证'安全模型
  8. 8 VPN是什么?虚拟专用网络与WireGuard、OpenVPN协议
  9. 9 MFA 是什么?多因素认证与 2FA 对比详解
  10. 10 什么是防火墙?在网络安全中的角色和功能
  11. 11 什么是SQL注入?数据库攻击与防护
  12. 12 什么是XSS?跨站脚本攻击与防护
✦ 快速摘要
SQL注入(SQLi)是OWASP A03:2021漏洞,允许攻击者将恶意SQL语句注入应用程序查询,从而访问或删除整个数据库。了解攻击机制、示例载荷及使用预处理语句的防护方法。
这篇文章怎么样?

SQL注入是最危险的Web安全漏洞,存在超过25年,但仍在OWASP 2021年十大漏洞列表中名列前茅。搜索框或登录表单中输入的一个单引号('),就可能让攻击者访问您应用程序的整个数据库——无需密码,无需特殊账户。

什么是SQL注入?OWASP A03:2021

**SQL注入(SQLi)**是一种攻击技术,攻击者将恶意SQL语句注入Web应用程序的输入参数。当应用程序处理不当时,数据库引擎会将这些语句作为查询的合法部分执行——导致数据泄露、身份验证绕过或数据破坏。

OWASP 2021年十大Web应用安全风险中,SQL注入属于A03:注入类别——这是最普遍、最严重的安全风险之一。根据OWASP数据,注入漏洞(包括SQLi、LDAP注入、OS命令注入)在94%的被测应用中被发现,发生率为19%。

为什么25年后SQLi仍然盛行?

SQL注入最早记录于20世纪90年代末。25年后,自然会有这样的问题:为什么这个漏洞还没有被完全消除?

**技术原因:**许多应用程序——尤其是遗留系统——在构建SQL查询时仍然使用直接字符串拼接。这对于初学者来说是自然的写法,如果没有代码审查或SAST(静态应用安全测试),很容易通过审查。

**人为因素:**截止日期的压力导致许多团队跳过最佳实践。此外,许多继承的代码库是在预处理语句成为行业标准之前编写的。

**广泛的攻击面:**应用程序从用户接收数据(表单、URL参数、Cookie、HTTP头)并将其传入SQL查询的任何地方,都是潜在的攻击点。

攻击机制:从输入到数据库

要理解SQLi,需要了解数据从用户输入到数据库执行查询的整个流程。

易受攻击应用中的数据流

  1. 用户在表单中输入数据(用户名、密码、搜索词...)
  2. 应用程序将输入直接拼接到SQL字符串中
  3. SQL字符串被发送到数据库引擎
  4. 数据库执行所有SQL——包括攻击者注入的部分

以下是经典的易受攻击PHP代码示例:

php
1// VULNERABLE — 不要这样做
2$username = $_POST['username'];
3$password = $_POST['password'];
4$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";

正常情况下,如果用户输入alicesecret123,查询将是:

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

这是一个有效且无害的查询。但当攻击者在用户名字段输入admin'--时会发生什么?

攻击载荷与影响

当攻击者输入admin'--作为用户名(密码输入任意内容)时:

SQL
1-- 被注入后的查询:
2SELECT * FROM users WHERE username='admin'--' AND password='anything'
3-- 之后的内容被注释掉 → 绕过密码验证

SQL中--是注释符号(MySQL中也可以用#)。整个AND password='anything'部分被忽略。数据库只检查用户名,如果admin账户存在,攻击者无需知道密码即可成功登录。

成功攻击的后果

根据应用程序使用的数据库用户的配置和权限,攻击者可以:

  • **读取敏感数据:**整个用户表、信用卡信息、医疗记录
  • **绕过身份验证:**以任何账户(包括管理员)登录
  • **修改/删除数据:**无限制的UPDATE或DELETE
  • **转储整个架构:**了解数据库结构以规划进一步攻击
  • **执行操作系统命令:**在MySQL上使用INTO OUTFILE,在SQL Server上使用xp_cmdshell

SQL注入的类型

并非所有SQLi都以相同的机制工作。攻击者根据应用程序处理和显示结果的方式使用不同的变体。

经典(带内)SQLi

这是最常见、最直接的形式。恶意SQL语句的结果直接在应用程序响应中返回——与原始请求使用相同的通道。

**基于错误的SQLi:**应用程序显示数据库的详细错误消息。攻击者利用错误消息中的信息了解数据库结构、版本和表名。

**基于UNION的SQLi:**攻击者使用UNION运算符将另一个SELECT的结果附加到原始结果中。这是从其他表转储数据最常见的方式。

盲布尔型SQLi

当应用程序不直接显示查询结果或错误消息时,攻击者仍然可以通过观察条件为TRUE还是FALSE时响应的差异来利用它。

例如:攻击者添加条件AND 1=1(TRUE)与AND 1=2(FALSE),并观察页面是否显示正常结果或为空。从那里,可以通过询问是/否问题逐位推断信息。

这个过程很慢,但可以用sqlmap等工具完全自动化。

盲时间型SQLi

类似于布尔型,但攻击者不是观察响应内容,而是测量服务器返回响应的时间。通过使用SLEEP()(MySQL)、WAITFOR DELAY(SQL Server)或pg_sleep()(PostgreSQL)函数,攻击者可以根据延迟推断信息。

例如:IF(1=1, SLEEP(5), 0)——如果服务器需要5秒才能响应,则条件为TRUE。

带外SQLi

最罕见的攻击类型,取决于数据库服务器创建出站网络连接(DNS查询、HTTP请求)的能力。攻击者不需要直接读取响应;相反,数据通过另一个通道发送出去(例如,向攻击者控制的域发送DNS查询)。

示例载荷(教育用途)

**重要提示:**以下载荷仅用于教育目的和安全意识。使用这些技术攻击您没有权限访问的系统是违法行为。只能在您控制的测试/实验室环境中应用。

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

每个载荷的解释

' OR '1'='1 — 用单引号关闭当前字符串,添加一个始终为真的条件。结果:WHERE子句始终为TRUE → 返回所有行。

admin'-- — 在用户名后关闭字符串,使用--注释掉查询的其余部分(通常是密码检查)。结果:绕过密码身份验证。

UNION SELECT — 将另一个SELECT附加到原始结果,以读取其他表中的数据(必须匹配列数和数据类型)。

SLEEP(5) — 用于时间型盲SQLi:通过测量延迟来确认漏洞存在。

DROP TABLE — 破坏性数据攻击。在实践中,许多数据库用户没有DROP权限——这就是最小权限原则重要的原因。

预防代码:预处理语句

预处理语句(也称为参数化查询)是防止SQL注入最有效、最可靠的方法。预处理语句不是将输入拼接到SQL中,而是将SQL结构数据完全分离。

工作原理:

  1. 应用程序向数据库发送SQL模板(带?:name占位符)
  2. 数据库编译解析SQL模板——此时查询结构已经固定
  3. 应用程序单独发送实际数据
  4. 数据库使用绑定到占位符的数据执行查询——数据无法改变查询结构

以下是在常用语言中的正确实现:

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();

为什么预处理语句是安全的?

当攻击者在用户名字段输入admin'--时,使用预处理语句:

  • 数据库已经知道查询结构:WHERE username = ? AND password = ?
  • admin'--作为字符串字面量传入,而不是SQL代码
  • 数据库将其视为普通字符串——包括'--
  • 不会发生注入

ORM和SQLAlchemy——安全还是不安全?

ORM(对象关系映射)如SQLAlchemy、Hibernate、ActiveRecord...提供了SQL之上的抽象层。一个常见问题:"我使用ORM了,还需要担心SQLi吗?"

答案:是的,仍然需要担心——如果您在ORM中使用原始SQL。

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)

分析

**ORM查询构建器(安全):**当您使用.filter(User.username == username)时,SQLAlchemy会自动在后台创建参数化查询。不会发生字符串拼接。

**原始f-string(不安全):**这是开发人员想要编写自定义SQL时最常见的错误。f-string将用户名直接拼接到SQL字符串中——这正是SQLi利用的模式。

**text()与命名参数(安全):**当需要原始SQL时,始终使用SQLAlchemy的text():param_name占位符,并通过字典传递值。SQLAlchemy将自动使用参数化查询。

关于存储过程的注意事项

存储过程不会自动防止SQLi。如果存储过程内部使用动态SQL(EXEC、sp_executesql)与字符串拼接,它仍然存在漏洞。请始终检查存储过程内部的代码。

WAF、输入验证及其为何不足够

Web应用防火墙(WAF)

WAF通过分析HTTP请求并阻止包含已知SQLi模式的请求(单引号、UNIONSELECTDROP等关键字)来工作。这听起来很有效,但有许多绕过技术:

编码绕过:%27'的URL编码。许多WAF在检查前不能正确解码。

注释插入:UN/**/ION——关键字中间的SQL注释。MySQL和某些其他数据库接受这种语法。

大小写变化:SeLeCtuNiOnsElEcT——使用区分大小写匹配的WAF会被愚弄。

双重编码:%2527 → 解码为%27 → 解码为'

**空白替代:**制表符、换行符、回车符可以替代SQL中的空格。

输入验证——双刃剑

一些团队尝试通过删除或转义特殊字符(如'";)来防止SQLi。问题在于:

  • 破坏合法数据:O'BrienD'Souza等用户名完全合法,但会被阻止。
  • **不够全面:**有数十种方法可以对SQLi载荷进行编码和混淆。
  • **虚假的安全感:**开发人员认为已经安全,而实际上漏洞仍然存在。

深度防御:正确策略

有效的SQLi防护需要多层保护:

  1. **预处理语句(必须):**这是基本且不可替代的措施。没有预处理语句,任何其他保护层都不够有效。

  2. **最小权限数据库用户:**应用程序使用的数据库账户应只具备最小必要权限(在特定表上的SELECT、INSERT、UPDATE)。永远不要为应用程序使用root/sa/admin账户。

  3. **WAF(补充):**检测并阻止已知攻击,减少日志中的噪音。但不是主要解决方案。

  4. **SAST/DAST:**静态应用安全测试,在开发过程中检测易受攻击的代码模式。

  5. **正确的错误处理:**不要向用户显示堆栈跟踪或详细的数据库错误消息。在服务器端记录错误,向客户端返回通用消息。

  6. **监控和告警:**检测异常模式(多次尝试包含'的输入、异常响应时间)以便及时响应。

OWASP参考资料与著名CVE

OWASP SQL注入防护备忘单

OWASP在OWASP SQL注入防护备忘单提供了详细的SQL注入防护指南。该文档涵盖:

  • 每种语言的预处理语句API列表
  • 存储过程——何时安全,何时不安全
  • 转义——仅在无法使用预处理语句时作为最后手段
  • 输入验证——如何正确执行
  • 最小权限建议

与SQL注入相关的著名CVE

CVE-2011-4505 — Joomla SQL注入: Joomla CMS中的SQLi漏洞影响了全球超过150万个网站。攻击者可以通过URL参数执行未经身份验证的SQL注入,读取包括管理员登录凭据在内的整个数据库。这是CMS历史上影响最广泛的CVE之一。

雅虎2012年数据泄露 — SQL注入: 2012年,黑客组织D33Ds Company宣布通过SQL注入从雅虎语音(前身为Associated Content)窃取了45万条登录凭据(明文用户名和密码)。这次泄露不仅影响了雅虎,还影响了其他服务,因为许多用户在多个账户中使用相同密码。

历史攻击的教训:

  • SQLi不仅针对小型应用程序——大公司和流行平台也可能受到影响。
  • 库/CMS中的单个SQLi漏洞可能影响运行该平台的数百万个网站。
  • 被盗数据通常被出售或公开披露——声誉损失通常大于直接经济损失。

总结:SQLi防护清单

在部署任何与数据库交互的功能之前,请检查:

  • 所有SQL查询都使用预处理语句/参数化查询
  • SQL代码中没有f-string/字符串拼接
  • ORM原始查询(如有)使用bindparam,而非f-string
  • 数据库用户只有最小必要权限
  • 错误消息不暴露数据库信息/堆栈跟踪
  • WAF已配置并正常运行(深度防御)
  • 代码已通过SAST扫描以检测注入模式

SQL注入是完全可以预防的。与许多其他复杂的安全漏洞不同,SQLi的解决方案简单明了:始终使用预处理语句,永远不要从用户输入中拼接SQL字符串。从一开始就养成正确的编码习惯,将完全消除这种风险。

什么是XSS?跨站脚本与防护

什么是防火墙?

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?