A Beginner's Guide to SQL Injection (And How to Prevent It)
You’re building a search feature for your app. Users type a product name, your backend queries the database, and results appear. Simple enough—until someone types '; DROP TABLE products;-- into that search box.
This is SQL injection, and it remains one of the most dangerous vulnerabilities in web development. The OWASP Top 10 lists “Injection” as a critical security risk, and SQL injection sits at the heart of that category. If you write code that touches a database—even occasionally—you need to understand how this attack works and how to stop it.
Key Takeaways
- SQL injection occurs when attackers manipulate database queries by inserting malicious code through user input
- Parameterized queries (prepared statements) are the primary defense, separating SQL code from data
- ORMs aren’t automatically safe when using raw query methods
- Use allowlists for dynamic identifiers like table and column names, which cannot be parameterized
- Apply defense in depth: least-privilege accounts, centralized data access layers, and security testing in CI pipelines
What Is SQL Injection?
SQL injection occurs when an attacker manipulates your application’s database queries by inserting malicious code through user input. Instead of treating input as data, the database executes it as commands.
Consider a login form. Your backend might construct a query like this:
SELECT * FROM users WHERE email = 'user@example.com' AND password = 'secret123'
If you build this query by concatenating strings with user input, an attacker can enter ' OR '1'='1 as the password. The resulting query becomes:
SELECT * FROM users WHERE email = 'user@example.com' AND password = '' OR '1'='1'
Since '1'='1' is always true, the query returns all users, bypassing authentication entirely.
Why SQL Injection Still Matters
You might assume modern frameworks have solved this problem. They haven’t—at least not automatically.
SQL injection vulnerabilities appear in:
- API endpoints accepting JSON with filter parameters
- Search forms with multiple query options
- Admin dashboards with dynamic sorting or filtering
- Report builders with user-defined criteria
Any place where user input influences a database query is a potential attack surface. The consequences range from data theft to complete database destruction.
How Unsafe Query Construction Happens
The root cause is always the same: treating user input as trusted SQL code rather than as data.
String concatenation is the primary culprit:
// Vulnerable - never do this
const query = `SELECT * FROM products WHERE name = '${userInput}'`;
Template literals and string formatting create the same problem:
# Vulnerable - never do this
query = f"SELECT * FROM products WHERE category = '{category}'"
Even ORMs aren’t automatically safe when you use raw query methods:
// Vulnerable - raw queries bypass ORM protections
db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
SQL Injection Prevention with Parameterized Queries
The primary defense against SQL injection is using parameterized queries (also called prepared statements). These separate SQL code from data, ensuring user input is never executed as commands.
Here’s the secure approach in JavaScript:
// Safe - parameterized query
const query = 'SELECT * FROM products WHERE name = ?';
db.query(query, [userInput]);
And in Python:
# Safe - parameterized query
cursor.execute("SELECT * FROM products WHERE name = %s", (user_input,))
The database treats the parameter as a literal value, not as SQL code. Even if someone enters '; DROP TABLE products;--, the database searches for a product with that exact string as its name.
What Parameterized Queries Don’t Cover
Identifiers (table names, column names) cannot be parameterized. If you need dynamic sorting:
// Use an allowlist for column names
const allowedColumns = ['name', 'price', 'created_at'];
const sortColumn = allowedColumns.includes(userInput) ? userInput : 'name';
const query = `SELECT * FROM products ORDER BY ${sortColumn}`;
Stored procedures are only safe if they avoid dynamic SQL internally. A stored procedure that concatenates strings is just as vulnerable.
Why Escaping Isn’t Enough
Manual string escaping and sanitization are insufficient on their own. They’re error-prone, database-specific, and easy to implement incorrectly. Parameterized queries handle escaping automatically and correctly.
Discover how at OpenReplay.com.
Defense in Depth
Parameterized queries are your primary defense, but additional layers help:
- Least-privilege database accounts: Your app’s database user shouldn’t have administrative permissions such as DROP, ALTER, or CREATE USER in production
- Centralized data access layer: Route all queries through a single module that enforces parameterization
- Code review and testing: Include SQL injection checks in your review process and CI pipeline
Modern guidance from OWASP ASVS and CISA’s “Secure by Design” initiative emphasizes building security into your development process rather than bolting it on afterward.
Conclusion
SQL injection remains dangerous because it’s easy to introduce and devastating when exploited. The fix is straightforward: use parameterized queries for all database operations, validate identifiers with allowlists, and never trust user input.
Make parameterized queries your default. Your future self—and your users—will thank you.
FAQs
Yes. NoSQL injection is a related vulnerability affecting databases like MongoDB. Attackers can manipulate query operators through user input. The defense is similar: use the database driver's built-in query methods rather than constructing queries from strings, and validate all user input before using it in queries.
ORMs provide protection when you use their standard query methods. However, most ORMs offer raw query functions that bypass these protections. If you use raw SQL methods or string interpolation within ORM queries, you're still vulnerable. Always use parameterized methods even within ORM code.
Use automated tools like SQLMap or OWASP ZAP to scan for vulnerabilities. Include security-focused unit tests that attempt injection payloads. Conduct code reviews specifically checking for string concatenation in queries. Consider penetration testing for critical applications before production deployment.
No. Input validation helps reduce attack surface but should never be your only defense. Clever attackers can often bypass validation rules. Parameterized queries are the primary defense because they fundamentally separate code from data. Use validation as an additional layer, not a replacement for proper query construction.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.