All articles
SecuritySQLBackendInterview12 min read

SQL Injection Explained: How It Works, When It Happens, and How to Stop It

What SQL injection is, the vulnerable code patterns that enable it, the most common attack types, and the layered defenses and interview framing that actually work.

What SQL injection actually is

SQL injection is an attack in which an attacker supplies input that changes the meaning of a SQL query the application sends to the database. If the application builds queries by dropping user input directly into a string, the database cannot tell where the developer's query ends and the attacker's code begins.

The result can be anything from reading another user's data to deleting entire tables or bypassing authentication entirely. It is not a database bug; it is an application-layer bug that lives in how the app composes SQL.

When it happens

SQL injection happens whenever an application treats user input as part of the SQL grammar instead of data.

  • String concatenation to build a query from form fields, URL parameters, or JSON bodies.
  • Dynamic sorting or column names interpolated from client input without an allow-list.
  • Stored procedures that use EXEC or dynamic SQL with unsanitized arguments.
  • Legacy reporting tools that expose raw SQL boxes to end users.
The common thread
The database receives a single string that contains both instructions and data. Because the instructions are assembled by the application, the attacker gets to write part of them.

A concrete attack walkthrough

Imagine a login endpoint that builds a query like this:

const query =
  "SELECT * FROM users WHERE email = '" + email + "' AND password = '" + password + "'";
Vulnerable backend code

An attacker sends the following email:

' OR '1'='1
Malicious input

The database receives:

SELECT * FROM users WHERE email = '' OR '1'='1' AND password = '...'
Resulting query

The condition '1'='1' is always true, so the query returns every user. With a small change the attacker can dump the whole schema:

' UNION SELECT null, table_name, null FROM information_schema.tables --
Union-based payload
Attacker' OR '1'='1Applicationstring concatenationDatabaseexecutes injected SQLData leakall rows returnedUntrusted input is concatenated directly into a query, so the database executes attacker-controlled SQL.
How a SQL injection request flows through the stack

Types of SQL injection

Not every SQLi attack returns data on the page. Attackers adapt to what the application reveals.

TypeHow it worksTypical use case
In-band / ClassicResults appear directly in the application's response, often via UNION SELECT.Dumping tables when error output or page content is visible.
Error-basedForces the database to throw an error that leaks schema or data details.Enumerating table or column names through error messages.
Boolean-based blindSends payloads that make the query true or false, then observes page differences.Exfiltrating one bit at a time when no direct output exists.
Time-based blindUses database functions like SLEEP or pg_sleep to delay responses conditionally.Extracting data when the page looks identical either way.
Out-of-bandTriggers the database to make an external network call with stolen data.Bypassing strict output controls by exfiltrating via DNS/HTTP.
Stored / Second-orderMalicious input is saved safely first, then executed later in a different query.Attacking reports, exports, or admin dashboards that reuse stored values.

How to prevent it

1. Parameterized queries / prepared statements

This is the single most effective defense. The query template is sent to the database first, and parameters are bound separately. The database treats them as values, never as executable SQL.

// Node.js with pg
const result = await pool.query(
  'SELECT * FROM users WHERE email = $1 AND password_hash = $2',
  [email, passwordHash]
);
Safe parameterized query
User inputtreated as data onlyPrepared statementquery template + paramsDatabaseparses once, binds safelySafeonly intended rowsParameters are sent separately from the SQL template, so the input can never change the query structure.
Parameterized input keeps data and commands separate

2. Use ORMs, but do not trust them blindly

ORMs parameterize by default, but they often expose raw query methods. Avoid those for user input. Also avoid passing strings into orderBy or select without an allow-list.

3. Allow-lists for dynamic identifiers

You cannot parameterize table names, column names, or sort directions. If you need them, keep an allow-list and reject anything not in it.

const allowedDirections = ['ASC', 'DESC'];
const direction = allowedDirections.includes(userDirection) ? userDirection : 'ASC';
Allow-list for sort direction

4. Input validation

Validate shape, length, and type before the value reaches the database. An email should look like an email, a UUID like a UUID. Validation is a defense-in-depth layer, not a replacement for parameterization.

Defense in depth

Even with prepared statements, add extra layers so a single mistake does not become a breach.

  • Least privilege: The application DB user should only have the permissions it needs. It should not be able to drop tables, read unrelated schemas, or write to admin tables.
  • Stored procedures with care: Stored procedures can help, but only if they do not build dynamic SQL from their arguments.
  • Web Application Firewall (WAF): A WAF can catch obvious SQLi signatures, but it is a safety net, not the primary defense. Skilled attackers craft payloads that bypass rules.
  • Disable verbose errors: Never expose raw database errors to end users. Log them internally and return a generic message.
  • Static analysis and dependency scanning: Tools like Semgrep, CodeQL, and dependency checkers can find unsafe query construction during development.
The golden rule
Treat SQL grammar as code and user input as data. Never let user input become grammar.

How to answer it in an interview

Interviewers want to hear that you understand the mechanism, the risk, and the layered fix.

  1. Define it simply: "SQL injection is when user input is interpreted as part of the SQL query instead of as data, letting an attacker alter what the database executes."
  2. Give an example: Walk through a login query with ' OR '1'='1 and explain how the condition becomes always-true.
  3. Explain the fix: Prepared statements / parameterized queries are the primary defense. Show the before-and-after code.
  4. Mention edge cases: Dynamic identifiers cannot be parameterized, so use allow-lists. Stored procedures help only if they avoid dynamic SQL.
  5. Talk about defense in depth: Least-privilege DB users, WAFs, input validation, and hiding detailed errors.
  6. Relate it to system design: In a large system, centralize data access behind repositories or query builders, never let services concatenate SQL, and audit raw-query usage in code review.

Summary

SQL injection survives because it is easy to introduce and devastating when exploited. The fix is not a single tool; it is a discipline: parameterize every query, validate input, allow-list dynamic identifiers, restrict database privileges, and add monitoring. If you can explain the mechanism and the layered defense with a short code example, you have already answered the interview question well.

Keep reading

Suggested next articles based on this one.

Design it, don't just read it.

Practise LLD and system design problems with structured rubrics and AI feedback.

Start practising free