SQL Injection, Explained Properly
SQL injection is an attack where user input, concatenated into a database query, gets executed as SQL instead of being treated as data. One vulnerable query can read, alter, or destroy an entire database. The complete defense has existed for decades: parameterized queries, which keep code and data on separate channels.
How does SQL injection actually work?#
A login check builds its query by gluing strings together:
// NEVER do this
const query =
"SELECT * FROM users WHERE name = '" + username +
"' AND password_hash = '" + hash + "'";
Now submit the username admin' --. The resulting SQL becomes:
SELECT * FROM users WHERE name = 'admin' --' AND password_hash = '...'
The -- starts a comment; the password check ceases to exist. The database did exactly what
it was told — the application just let an attacker do the telling. Every variant of the
attack, from authentication bypass to full data extraction with UNION SELECT, is this same
confusion scaled up.
Why do parameterized queries end the problem?#
Because they change the channel, not the characters. With a parameterized (prepared) query, the SQL text — with placeholders — is compiled first, and the input is bound afterward as pure data. The input is never parsed as SQL, so no sequence of quotes or comments can change the query’s structure:
const result = await db.query(
'SELECT * FROM users WHERE name = $1 AND password_hash = $2',
[username, hash],
);
| Approach | What the database receives | Injectable? |
|---|---|---|
| String concatenation | One string: code and data mixed | Yes |
| Manual escaping | One string: data disguised as safe | Historically bypassed |
| Parameterized query | Code and data on separate channels | No |
| Stored procedure (static) | Precompiled code, bound parameters | No |
Where does SQL injection still happen in modern stacks?#
Modern frameworks parameterize by default, so today’s injections hide in the exceptions:
raw-query escape hatches (sequelize.query, Django’s raw(), JPA native queries), string-built
ORDER BY clauses, analytics dashboards that accept “custom filters,” and legacy code nobody
has read since the framework migration. Code review for injection is therefore cheap: search
for query construction that touches request data, and audit every hit.
How does this connect to the rest of web security?#
SQL injection and cross-site scripting are the same disease in two organs: untrusted data reaching an interpreter — the database in one case, the browser in the other. The cure is also parallel: keep data out of the code channel (parameters there, output encoding here). Round out the defensive picture with the HTTP security headers guide, and see where injection sits in the full landscape at the Web Security hub.
Frequently asked questions#
Is SQL injection still a real threat in 2026?
Yes. Injection remains in the OWASP Top 10, and SQL injection specifically continues to appear in breach reports and CISA advisories, usually in legacy code, hand-built query strings, and ORM escape hatches. The defense has been known for decades; the gap is adoption, not knowledge.
Do ORMs make SQL injection impossible?
No. ORMs parameterize the queries they generate, which removes most risk, but nearly every ORM offers a raw-query escape hatch. The moment a developer concatenates input into a raw fragment, the vulnerability returns. Audit raw() calls, not the ORM.
Does escaping quotes prevent SQL injection?
Escaping is fragile: it depends on charset, database dialect, and context, and history is full of bypasses. Parameterized queries are categorically different — the input is never parsed as SQL at all, so there is nothing to bypass. Use escaping only where parameters are impossible, such as identifiers.