Cloud Tech by Victor
SecurityBeginner

SQL Injection Prevention

How SQL injection actually works, why parameterized queries are the real fix (not string escaping), and the defense-in-depth practices that back them up.

Updated 2026-07-083 min read

Overview

SQL injection happens when untrusted input is concatenated directly into a SQL query string, letting an attacker change the query's meaning, turning a login check into an always-true condition, or appending a second statement that drops a table. It remains one of the most common serious vulnerabilities because the naive fix (string concatenation) looks like it works right up until someone sends input designed to break out of it. The real fix is structural: keep user input and SQL syntax in separate channels entirely, via parameterized queries, so there is no string to break out of.

Quick Reference

ApproachSafe?Why
String concatenationNoUser input becomes part of the SQL text itself
Manual escapingRiskyEasy to miss a context (identifiers, LIKE patterns, numeric fields)
Parameterized queries / prepared statementsYesInput is bound as data, never parsed as SQL syntax
ORM query builder methodsYes (for generated queries)Parameterized internally; raw-SQL escape hatches still need care
Allowlisting for identifiers (table/column names)YesIdentifiers cannot be parameterized, must be validated against a fixed set

Syntax

javascript
// VULNERABLE - user input concatenated directly into the query
const query = `SELECT * FROM users WHERE email = '${email}'`
// email = "' OR '1'='1" turns this into an always-true condition

// SAFE - parameterized query, input sent separately from the SQL text
const result = await db.query('SELECT * FROM users WHERE email = $1', [email])

Never trust client-side escaping

Client-side validation and JavaScript-based escaping can always be bypassed by an attacker calling your API directly; they're a UX nicety, not a security boundary. The server must parameterize independently of anything the client claims to have sanitized.

Examples

python
# Python (psycopg2) - parameterized, safe
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

# Never do this - f-string interpolation into raw SQL
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")  # vulnerable
sql
-- Identifiers (table/column names) cannot be parameterized at all;
-- they must be validated against a fixed allowlist, never taken from
-- raw user input, even with an otherwise-parameterized query.
-- e.g. sortColumn must be checked against ['name', 'created_at'] in code
-- before being interpolated into: ORDER BY {sortColumn}

Visual Diagram

Common Mistakes

  • Relying on manual escaping or blocklisting specific characters instead of parameterized queries; it is fragile and context-dependent.
  • Assuming an ORM makes an application immune, while still using its raw-SQL escape hatch with string interpolation somewhere.
  • Trying to parameterize a table or column name, parameter binding only works for values, not identifiers; those need allowlisting instead.
  • Building LIKE patterns or IN (...) lists via string concatenation instead of using the driver's parameter-array support.
  • Trusting client-side validation as a security boundary; it can always be bypassed; the server must validate and parameterize independently.

Performance

  • Prepared statements can be reused across executions with different parameter values, which lets the database cache the query plan, parameterized queries are often faster, not just safer, under repeated execution.
  • Parameter binding adds negligible overhead compared to string concatenation; there is no meaningful performance reason to avoid it.

Best Practices

  • Use parameterized queries or an ORM's query builder for every query that includes user-supplied values, no exceptions for "just this one internal tool."
  • Allowlist (never blocklist) any user-influenced identifier, like a sort column or table name, against a fixed set of valid values.
  • Apply least-privilege database accounts per service, so even a successful injection has limited blast radius.
  • Run static analysis or a linter rule that flags raw string interpolation into SQL, catching the mistake before it reaches production.

Interview questions

Why does string escaping alone not fully prevent SQL injection?

Escaping tries to neutralize special characters (like quotes) so user input cannot break out of its intended string literal, but it is easy to get wrong, different databases and contexts (string literals, numeric contexts, identifiers, LIKE patterns) have different escaping rules, and a single missed case reopens the vulnerability. Parameterized queries avoid the problem entirely: user input is sent to the database separately from the query structure, so it can never be interpreted as SQL syntax regardless of its content.

What is the difference between a parameterized query and simply concatenating a sanitized string?

A parameterized query sends the SQL command and the user-supplied values as two separate things to the database driver, the driver (or the database itself, for prepared statements) binds the values into the query plan without ever treating them as part of the SQL text. String concatenation, even "sanitized," still builds one text string where user input and SQL syntax share the same channel, any gap in the sanitization logic can be exploited. Parameterization removes that shared channel entirely rather than trying to police it.

Can an ORM fully prevent SQL injection on its own?

An ORM prevents injection for the queries it builds using its own query API, because those are parameterized under the hood. It does not protect against injection if raw SQL is still used, for example, string-interpolating a value into a raw query method, or building dynamic column/table names from user input (which parameterization cannot help with, since identifiers cannot be bound as parameters and need allowlisting instead). ORMs reduce the attack surface; they do not eliminate the need to think about it.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement