SQL Injection Prevention
SQL injection (SQLi) happens when untrusted input is interpreted as part of an SQL query instead of as data. It remains one of the most damaging web vulnerabilities — a single unparameterized query can lead to data exfiltration, data corruption, or full system compromise.
The fix is well understood and cheap: treat user input as data, never as SQL. Almost every real-world SQLi bug traces back to building a query with string concatenation.
TL;DR
- Use parameterized queries / prepared statements everywhere.
- Never build SQL with string concatenation or template literals.
- ORMs help but aren't automatic immunity — they're unsafe if you interpolate.
- Allowlist anything that can't be a parameter (column names, sort direction).
- Run the app with a least-privilege database role and monitor for anomalies.
Quick Example
The single most important habit — pass values as parameters, not as part of the SQL string:
How Injection Happens
When input is concatenated into a query, an attacker can change its meaning:
If email is x' OR '1'='1, the database sees:
The database can't tell your intent from the attacker's — the boundary between code and data was lost the moment you concatenated.
Common SQLi Types
- Classic / in-band — results come straight back in the response (as above).
- Blind — no data is returned, but the attacker infers it from boolean or timing differences.
- UNION-based — the attacker appends a
UNION SELECTto pull data from other tables.
The Real Fix: Parameterization
Use placeholders and bind values as parameters. The syntax differs by stack, but the principle is identical:
With an ORM or query builder, keep every value a bound parameter — never interpolate user input into a raw fragment.
The Parts You Can't Parameterize
Parameters bind values, not identifiers. Column names, table names, and sort direction can't be parameters — and that's where teams who "always parameterize" still get burned:
Defense in Depth
Parameterization is the fix; these limit the damage if something slips through.
Least privilege
- Use separate read/write roles; don't run the app as a superuser.
- Grant only the tables and operations the app actually needs.
Observability
- Log slow and failing queries; alert on error spikes.
- Watch for unusual patterns (sudden
UNION, bursts of failed parses).
See Logging for structured logging patterns.
Common Mistakes
Concatenating input into SQL
Trusting a "we use an ORM" assumption
FAQ
Does using an ORM make me safe from SQL injection?
Mostly, for normal CRUD — but not automatically. The moment you drop to raw SQL or interpolate a value into a query fragment, you can reintroduce SQLi. Keep all values as bound parameters.
Isn't escaping input enough?
No. Hand-rolled escaping is fragile and easy to get wrong across encodings and database quirks. Parameterized queries remove the need to escape because input never enters the SQL grammar.
How do I parameterize a dynamic column name or sort order?
You can't — identifiers aren't parameters. Map user input through an allowlist of known-safe column names and reject anything else.
What about search with LIKE?
Still parameterize: bind the whole pattern (e.g. %term%) as a value. Build the wildcards in application code, not by concatenating into the SQL.
Related Topics
- API Security — Input validation at the endpoint
- Databases — Roles, permissions, and query design
- Application Security — Where SQLi sits in the OWASP Top 10
- Logging — Detecting injection attempts