How to Prevent SQL Injection
SQL injection allows attackers to execute arbitrary SQL commands on your database. It can lead to data theft, data modification, or complete database destruction. This guide covers how to prevent SQL injection using parameterized queries, ORMs, and proper input validation.
Find security issues automatically before attackers do.
Follow These Steps
Never use string concatenation for SQL queries
The root cause of SQL injection is inserting user input directly into SQL strings.
// VULNERABLE - string concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`
// Attacker input: ' OR 1=1 --
// Result: SELECT * FROM users WHERE email = '' OR 1=1 --'
// VULNERABLE - template literals
const query = `SELECT * FROM users WHERE id = ${id}`
// Attacker input: 1; DROP TABLE users;
// Result: SELECT * FROM users WHERE id = 1; DROP TABLE users;Use parameterized queries
Parameterized queries separate SQL code from data, making injection impossible.
// Node.js with pg
const result = await pool.query(
'SELECT * FROM users WHERE email = $1',
[email]
)
// Node.js with mysql2
const [rows] = await connection.execute(
'SELECT * FROM users WHERE email = ?',
[email]
)
// Python with psycopg2
cursor.execute(
"SELECT * FROM users WHERE email = %s",
(email,)
)Use an ORM for type-safe queries
ORMs like Prisma, Drizzle, and SQLAlchemy generate parameterized queries automatically.
// Prisma - always parameterized
const user = await prisma.user.findUnique({
where: { email: email }
})
// Drizzle - always parameterized
const user = await db.select().from(users).where(eq(users.email, email))
// CAUTION: Raw queries in ORMs can still be vulnerable
// Prisma raw query - still safe with template
const users = await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`
// UNSAFE raw query
const users = await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`)Validate input types and formats
Validate that input matches expected types before using in queries.
import { z } from 'zod'
const SearchParams = z.object({
id: z.coerce.number().int().positive(),
email: z.string().email().max(255),
status: z.enum(['active', 'inactive', 'pending'])
})
// Only query with validated data
const params = SearchParams.parse(req.query)
const result = await pool.query(
'SELECT * FROM users WHERE id = $1 AND status = $2',
[params.id, params.status]
)Use least-privilege database users
Your application database user should only have the permissions it needs.
-- Create a restricted database user
CREATE USER app_user WITH PASSWORD 'strong-password';
-- Grant only necessary permissions
GRANT SELECT, INSERT, UPDATE ON users TO app_user;
GRANT SELECT, INSERT ON posts TO app_user;
-- Never use the superuser account in your application
-- REVOKE DROP, CREATE, ALTER from the app userTest for SQL injection vulnerabilities
Use automated tools and manual testing to find injection points.
# Manual testing payloads (use in test environment only)
# Text fields: ' OR 1=1 --
# Numeric fields: 1 OR 1=1
# Search fields: %; DROP TABLE users; --
# Run VAS scan to detect SQL injection vulnerabilities automaticallyWhat You'll Achieve
All database queries use parameterized queries or an ORM. Input is validated before querying. The database user has minimal permissions. Your application is protected against SQL injection.
Common Mistakes to Avoid
Mistake
Using $queryRawUnsafe or equivalent methods
Fix
Always use parameterized raw queries. In Prisma, use $queryRaw with template literals, not $queryRawUnsafe with string concatenation.
Mistake
Thinking ORMs are automatically safe
Fix
ORMs are safe for their standard query methods, but raw query methods can still be vulnerable. Always use parameterized syntax for raw queries.
Mistake
Validating input as the only defense
Fix
Input validation is defense-in-depth but not the primary defense. Parameterized queries make injection impossible regardless of input.
Frequently Asked Questions
Are ORMs immune to SQL injection?
Standard ORM methods (findUnique, select, where) are safe. But raw query methods like $queryRawUnsafe in Prisma or raw() in some ORMs can be vulnerable if you use string concatenation.
Does input validation prevent SQL injection?
It reduces attack surface but is not sufficient alone. Parameterized queries are the primary defense because they make injection structurally impossible, regardless of input content.
Can NoSQL databases get SQL injection?
Not SQL injection specifically, but NoSQL injection exists. MongoDB, for example, is vulnerable to operator injection if user input is passed directly to query operators. Always validate input.
Ready to Secure Your App?
VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.
Start Security Scan