SQL Injection
SQL injection occurs when untrusted input changes query structure. Parameterized queries separate code from data and are the primary defense across languages and databases.
Definition
SQL injection is a vulnerability where an application concatenates untrusted input into a SQL statement, allowing an attacker to alter the query's structure. The attacker may bypass authentication, read or modify unauthorized rows, or execute database functions. Parameterized queries (prepared statements) prevent it by sending the query template and values separately, so input is always treated as data rather than SQL syntax.
Context and scope
SQL injection applies to any database access path that builds queries from strings, including ORM raw-query escape hatches, reporting tools, and migration scripts. It is not limited to web forms; any input source that reaches a query builder can be an entry point.
Common misconceptions
- Escaping quotes is enough — encoding rules vary by database and connection settings, and numeric or identifier contexts are different
- ORMs make injection impossible — raw SQL, unsafe order-by fragments, and dynamic identifiers can still be injectable
- Stored procedures are always safe — dynamic SQL inside a procedure can be vulnerable if it concatenates input
Anti-patterns
- Building WHERE clauses with string concatenation or template literals
- Interpolating table or column names from user input without an allowlist
- Logging full SQL containing secrets or personal data
Related concepts
Related examples
Authoritative source
How injection happens
# ❌ Input changes the query structure
query = "SELECT * FROM users WHERE email = '" + email + "'"
cursor.execute(query)
An input such as ' OR '1'='1 turns the condition into a tautology. The database cannot tell that the value was meant to be data.
Parameterized queries
# ✅ Driver sends template and values separately
cursor.execute("SELECT * FROM users WHERE email = ?", (email,))
// ✅ Node.js pg example
await pool.query('SELECT * FROM users WHERE email = $1', [email]);
Identifiers such as table and column names cannot be parameterized in most databases. Map user choices to a fixed allowlist of known identifiers instead of interpolating them.
Defense in depth
- Use prepared statements for every value, including numbers and dates.
- Use an ORM’s query builder for dynamic filters, and review raw-query escape hatches.
- Apply least-privilege database accounts so a successful injection cannot read unrelated schemas.
- Validate input shape at the boundary, but never treat validation as a substitute for parameterization.
When it matters
Every query that includes external input is a candidate, including admin search, exports, and background jobs. Compared to XSS, SQL injection targets the database and often affects other users’ data; XSS executes in a victim’s browser. Both are prevented by separating data from code, not by hiding error messages.
Pitfalls
Quote escaping breaks under different character sets and connection modes. String-building in stored procedures or reporting tools is still injection. Tests that only check the happy path miss the vulnerability; include inputs containing quotes, comments, and boolean tautologies.