SQL Injection Explained: How Attackers Manipulate Your Database

Share

TL;DR

SQL injection happens when user input gets treated as SQL code instead of data. Attackers can read your entire database, modify records, or delete everything. The fix is simple: use parameterized queries or prepared statements. Never build SQL strings by concatenating user input. Modern ORMs like Prisma handle this automatically.

What Is SQL Injection?

SQL injection (SQLi) is a vulnerability where attackers can execute arbitrary SQL commands on your database by manipulating user input. It's been on the OWASP Top 10 for over 20 years because it's both common and devastating.

Here's the classic example:

Vulnerable login query
// User enters: admin' --
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;

// The query becomes:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'

// Everything after -- is a comment, so password check is skipped!
// Attacker logs in as admin without knowing the password

How SQL Injection Works

The Vulnerable Pattern

SQL injection is possible when you build queries using string concatenation:

Vulnerable code patterns
// VULNERABLE: String concatenation
const query = `SELECT * FROM products WHERE id = ${productId}`;
const query = "SELECT * FROM users WHERE email = '" + email + "'";

// VULNERABLE: Template literals with user input
const query = `DELETE FROM orders WHERE id = ${req.params.id}`;

Common Attack Payloads

PayloadPurposeResult
' OR '1'='1Always true conditionBypass authentication
'; DROP TABLE users; --Execute additional statementDelete entire table
' UNION SELECT * FROM passwords --Combine with another queryExtract sensitive data
1; UPDATE users SET admin=1 WHERE id=5Modify dataPrivilege escalation

Real-World SQL Injection Attacks

Data Extraction

Attacker extracts all user data
// Search field input: ' UNION SELECT username, password, email FROM users --

// Original query:
SELECT name, description FROM products WHERE name LIKE '%search%'

// Becomes:
SELECT name, description FROM products WHERE name LIKE '%'
UNION SELECT username, password, email FROM users --%'

// Returns all usernames, passwords, and emails!

Authentication Bypass

Logging in without credentials
// Login form:
Username: admin' --
Password: (anything)

// Query becomes:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'x'

// The password check is commented out

Impact: In 2023, SQL injection was responsible for 33% of critical web application vulnerabilities. A single SQLi vulnerability can expose your entire database.

How to Prevent SQL Injection

1. Use Parameterized Queries

Parameterized queries (prepared statements) separate SQL code from data:

Safe parameterized queries
// PostgreSQL with node-postgres
const query = 'SELECT * FROM users WHERE email = $1 AND password = $2';
const result = await pool.query(query, [email, password]);

// MySQL with mysql2
const query = 'SELECT * FROM users WHERE email = ? AND password = ?';
const [rows] = await connection.execute(query, [email, password]);

// SQLite with better-sqlite3
const stmt = db.prepare('SELECT * FROM users WHERE id = ?');
const user = stmt.get(userId);

2. Use an ORM

ORMs use parameterized queries automatically:

Safe ORM usage
// Prisma (safe by default)
const user = await prisma.user.findUnique({
  where: { email: userInput }
});

// Drizzle (safe by default)
const users = await db.select().from(users).where(eq(users.email, userInput));

// Sequelize (safe by default)
const user = await User.findOne({ where: { email: userInput } });

ORM raw queries are still dangerous: Methods like prisma.$queryRaw, sequelize.query(), or Knex raw queries can still be vulnerable if you concatenate user input. Always use the parameterized versions.

3. Validate and Sanitize Input

Input validation before queries
import { z } from 'zod';

// Define expected input format
const userIdSchema = z.string().uuid();
const emailSchema = z.string().email();

// Validate before using in queries
const validatedId = userIdSchema.parse(req.params.id);
const validatedEmail = emailSchema.parse(req.body.email);

// Now safe to use (though still use parameterized queries!)

4. Use Least Privilege Database Accounts

Limit the damage if SQLi occurs by restricting database permissions:

Database user permissions
-- Create a limited user for the application
CREATE USER app_user WITH PASSWORD 'secure_password';

-- Only grant necessary permissions
GRANT SELECT, INSERT, UPDATE ON products TO app_user;
GRANT SELECT ON categories TO app_user;

-- Don't grant DELETE or DROP permissions unless needed
-- NEVER use the database superuser for your app

SQL Injection in AI-Generated Code

AI assistants often generate SQL injection vulnerabilities:

What AI might generate
// AI often produces string concatenation
app.get('/user/:id', async (req, res) => {
  const query = `SELECT * FROM users WHERE id = ${req.params.id}`;
  const result = await db.query(query);
  res.json(result);
});

// What you should ask for:
app.get('/user/:id', async (req, res) => {
  const query = 'SELECT * FROM users WHERE id = $1';
  const result = await db.query(query, [req.params.id]);
  res.json(result);
});

Prompt tip: When asking AI to generate database queries, specifically request "parameterized queries" or "prepared statements" to get secure code.

What is SQL injection?

SQL injection is a vulnerability where attackers insert malicious SQL code through user input fields. If the application builds SQL queries by concatenating user input directly, attackers can manipulate the query to access, modify, or delete database data.

Does using an ORM prevent SQL injection?

ORMs like Prisma, Drizzle, and Sequelize use parameterized queries by default, which prevents most SQL injection. However, raw query methods (like prisma.$queryRaw) can still be vulnerable if you concatenate user input directly.

Can NoSQL databases have injection vulnerabilities?

Yes, NoSQL databases like MongoDB can have injection vulnerabilities called NoSQL injection. Attackers can manipulate queries using operators like $gt or $ne if user input isn't properly validated.

Is input validation enough to prevent SQL injection?

Input validation helps but isn't sufficient alone. Always use parameterized queries as your primary defense. Validation is a secondary layer that catches obvious malicious input and prevents other types of attacks.

Scan for SQL Injection Vulnerabilities

Our scanner detects SQLi patterns in your code and tests your endpoints.

Start Free Scan
Vulnerability Guides

SQL Injection Explained: How Attackers Manipulate Your Database