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.
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)
-- 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 same check, rewritten safely (placeholder syntax varies by driver)
-- 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
?, $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 BYclauses) cannot always be parameterized the normal way; where dynamic identifiers are unavoidable, validate them against a strict allow-list rather than inserting raw input.