RestingOwl owl logo RestingOwl

SQL Injection:How It Works & How to Prevent It

Quick Answer: SQL injection (SQLi) is an attack where untrusted input is inserted into a database query, letting an attacker change what the query does: reading, modifying, or deleting data they should not have access to. It happens when applications build queries by concatenating strings instead of using parameters. The primary fix is parameterized queries (prepared statements), which keep user data strictly separate from SQL code. SQLi has been an OWASP Top 10 entry for over two decades.

SQL injection is one of the oldest web vulnerabilities still causing breaches today. It is also one of the easiest to prevent completely: the fix is well understood and built into every modern database driver. This guide shows how SQLi works, the main types, and how to shut it down for good. It is part of our injection series alongside cross-site scripting and prompt injection.

What Is SQL Injection?

A SQL injection vulnerability exists when an application takes user input and places it directly into a database query without separating data from code. The database cannot tell the difference between the SQL the developer intended and the SQL an attacker supplied: it executes both. This lets attackers read other users' data, bypass authentication, modify records, or in severe cases run commands on the database server.

How Does SQL Injection Work?

Consider a login query built by string concatenation. The developer expects a username and password; the attacker supplies SQL instead:

// VULNERABLE: input concatenated into the query
const q = `SELECT * FROM users
          WHERE name = '${user}' AND pass = '${pass}'`;

// Attacker enters as the username:   admin' --
// Resulting query:
// SELECT * FROM users WHERE name = 'admin' --' AND pass = '...'

The -- comments out the rest of the query, so the password check disappears entirely and the attacker logs in as admin. A payload like ' OR '1'='1 achieves the same by making the WHERE clause always true.

What Are the Types of SQL Injection?

TypeHow It WorksWhen It Is Used
In-band (Classic)Results come back in the same response: error messages or UNION-based data extractionWhen the app shows query output or errors
Blind (Inferential)No data returned; attacker infers answers from boolean responses or time delaysWhen errors and output are suppressed
Out-of-bandData is exfiltrated over a separate channel such as a DNS or HTTP requestWhen the app is slow or responses are not visible

What Can an Attacker Do With SQL Injection?

  • Authentication bypass: log in without valid credentials.
  • Data theft: dump entire tables of users, passwords, payment data, or PII.
  • Data tampering: modify or delete records, including audit logs.
  • Privilege escalation: read or change administrative accounts.
  • Remote code execution: on some database configurations, run OS commands or read server files.

How Do You Prevent SQL Injection?

1. Use Parameterized Queries (Primary Defense)

Parameterized queries (prepared statements) send the SQL and the data to the database separately, so user input is always treated as a value, never as code. This single change eliminates the vast majority of SQLi:

// SAFE: values passed as parameters, never concatenated
const sql = 'SELECT * FROM users WHERE name = $1 AND pass = $2';
const result = await db.query(sql, [user, pass]);

2. Use an ORM or Query Builder Correctly

ORMs like Prisma, Sequelize, and TypeORM parameterize queries by default. They are safe as long as you do not drop down to raw string-concatenated SQL. When you must write raw queries, still use the ORM's parameter binding rather than template literals.

3. Apply Least Privilege

The database account your app uses should have only the permissions it needs. An application that only reads and writes its own tables should not connect as a superuser. Least privilege limits the blast radius if an injection does slip through.

4. Validate Input and Use Allowlists

For inputs that cannot be parameterized: such as table or column names in dynamic queries: validate against a strict allowlist of permitted values. Never pass user input directly into identifiers. Input validation is a secondary layer; it does not replace parameterization.

SQL Injection vs XSS: What Is the Difference?

DimensionSQL InjectionXSS
Target layerThe databaseThe victim's browser
Injected codeSQL commandsJavaScript / HTML
Primary impactData theft, auth bypassSession theft, account takeover
Primary defenseParameterized queriesOutput encoding + CSP

Both are injection attacks: untrusted data treated as code: but they hit different layers and need different fixes. For the browser-side equivalent, see our XSS guide.

SQL Injection Prevention Checklist

  • Use parameterized queries / prepared statements for every query that includes user input
  • Never build SQL with string concatenation or template literals containing user data
  • Use an ORM's parameter binding, even for raw queries
  • Run the application's database account with least privilege
  • Allowlist any user input used as table or column names
  • Disable detailed database error messages in production
  • Add SQLi tests to CI and run DAST tools such as OWASP ZAP
  • Use a web application firewall as a defense-in-depth layer, not a primary control
Go deeper: Find injection risks before they ship with threat modeling, and see the other half of the injection family in prompt injection, the SQL injection of the AI era.

References

  1. 1OWASP SQL Injection Prevention Cheat Sheet
  2. 2OWASP SQL Injection
  3. 3OWASP Query Parameterization Cheat Sheet

Q&A Section

Parameterized queries (prepared statements) are the primary and most effective defense. They send the SQL command and the user data to the database separately, so input is always treated as a value and never executed as code. Combine this with least-privilege database accounts and input allowlisting for identifiers that cannot be parameterized.
Mostly yes. ORMs like Prisma, Sequelize, and TypeORM parameterize queries by default, so normal use is safe. The risk returns the moment you write raw SQL with string concatenation. If you need raw queries, use the ORM's parameter binding rather than building the query string yourself.
Yes. Despite being decades old and easy to prevent, SQL injection still causes real breaches because legacy code, dynamic queries, and string concatenation persist in production systems. It remains part of the Injection category in the OWASP Top 10.
Blind SQL injection is a variant where the application does not return query results or error messages. The attacker infers information indirectly, for example by asking true/false questions and observing whether the page behaves differently, or by injecting time delays and measuring the response time. It is slower to exploit but just as dangerous.
Copied!