SQLSQL Injection & Prevention

SQL Injection & Prevention

SQL injection is what happens when untrusted input — something a user typed into a form, a URL parameter, an API field — gets pasted directly into a SQL query string instead of being treated as pure data. If that input contains SQL syntax of its own, it can change what the query actually does, letting an attacker read data they shouldn't see, modify or delete data, or in the worst cases take control of the underlying server.

One of the most damaging vulnerabilities in software
SQL injection has appeared on the OWASP Top 10 list of critical web application security risks for over two decades, and it continues to show up in real breaches every year — often because a single query somewhere in a large codebase built its SQL by concatenating strings. This page explains the vulnerability purely so you can recognize and prevent it in your own code; it is not a guide to attacking anything.
How the vulnerability happens

Imagine a login check written by building the query as a plain string and inserting whatever the user typed straight into it:

Illustrative vulnerable pattern (do not use)

SQL
-- application pseudocode:
-- query = "SELECT * FROM users WHERE username = '"
--        + userInput
--        + "' AND password = '" + passwordInput + "'"

-- if userInput is the single word:  admin
-- the resulting query is harmless:
SELECT * FROM users WHERE username = 'admin' AND password = 'whatever';

-- but if userInput is:  admin' --
-- the resulting query becomes:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'whatever';
-- everything after -- is now a SQL comment, so the password check
-- never actually runs

The problem is structural: the moment untrusted text is spliced into the query string, that text is no longer just data to the database — it becomes part of the SQL syntax itself. A well-placed quote, comment marker, or extra clause can quietly rewrite the query's logic.

The fix: parameterized queries
The definitive fix is to never build SQL by concatenating input into a string. Instead, use parameterized queries (also called prepared statements), where the query text and the user-supplied values are sent to the database separately. The database knows exactly where each placeholder starts and ends, so a value can never be interpreted as SQL syntax — no matter what characters it contains.

The same check, rewritten safely (placeholder syntax varies by driver)

SQL
-- the query text is fixed and never touched by user input:
SELECT * FROM users WHERE username = $1 AND password = $2;

-- the actual values are passed alongside the query, as parameters:
-- params: ["admin' --", "whatever"]
-- the database treats the entire string "admin' --" as one literal
-- value to compare against, not as SQL — the injection has no effect
Virtually every database driver and language supports parameterized queries — placeholders may look like ?, $1, or :name depending on the library, but the principle is identical: values travel separately from the query structure.
Additional layers of defense
  • Principle of least privilege — the database account your application connects with should only have the privileges it actually needs, so even a successful injection has limited blast radius.

  • Input validation — reject or normalize input that clearly should not contain SQL-like content, such as a numeric ID field that receives non-numeric text. Validation is a useful extra layer, but it is not a substitute for parameterized queries.

  • ORMs and query builders — tools like an ORM generate parameterized queries for you by default, which removes an entire class of mistakes, but be aware that most ORMs still offer an escape hatch for raw SQL, and that raw SQL needs the same parameterization discipline.

  • Avoid dynamic SQL where possible — features that build query text dynamically (table names, column names, ORDER BY clauses) cannot always be parameterized the normal way; where dynamic identifiers are unavoidable, validate them against a strict allow-list rather than inserting raw input.

Parameterization also fixes correctness, not just security
Even without a malicious attacker, string concatenation breaks on perfectly innocent input — a last name containing an apostrophe, like O'Brien, will break a concatenated query the same way a deliberate attack would. Parameterized queries fix both problems at once.