Login
ChallengesLearn
Scoreboard
Teams
SPNZ

LearnSQL InjectionSQL Injection: Why it's possible
SQL Injection·Lesson 3 of 20

SQL Injection: Why it's possible

The exact mechanism - why a string from a stranger gets treated as code by your database. The conceptual leap that turns login bypass from magic into a logical consequence.

Beginner10 min
SQLiTrust BoundaryAST
Loading lesson…
PreviousSQL Injection: How databases workNextSQL Injection: Authentication bypass

© 2026 SPNZ.

Terms of ServicePrivacy PolicyCookie Policy

The vulnerability is a single line in the application. The database is doing what it is told. The bug lives at the boundary where string values from the network are pasted into a program that the database then runs.

Prerequisites
Read these lessons first:
  • L1Fundamentals
  • L2How databases work
What you'll be able to do
  • Identify the exact line of code that turns user input into SQL grammar.
  • Explain why a single apostrophe is enough to break a SQL query.
  • Distinguish code from data at the SQL boundary.
  • Describe what the database is doing when it sees a string with unbalanced quotes.
Key terms
Trust boundary
The line where data crosses from "user-controlled" to "trusted". In SQLi it is the spot where a form value is concatenated into a query string.
Abstract syntax tree (AST)
The tree the parser builds from SQL tokens. String concatenation at the application level happens before the parser ever runs - so the AST reflects attacker intent, not developer intent.
Quoted identifier
A value the database treats as a literal because it is wrapped in single quotes. Crossing the quote boundary turns a literal into code.
Tautology
A WHERE clause that is always true (1=1, "a"="a"). One of the most common SQLi primitives - it converts a filter into a no-op.
What is it?

The line of code that creates the bug

Almost every SQLi vulnerability comes from one of two patterns:

javascriptvulnerable
// Pattern A - string concatenation
const query = "SELECT * FROM users WHERE id = " + userId;

// Pattern B - template literal
const query = `SELECT * FROM users WHERE username = '${username}'`;

Both patterns look like programming - and they are. The application is building a program: a SQL query that the database will parse and execute. The user's value is being pasted into that program as if it were trusted source code. It is not. It is data that crossed a trust boundary.

The fix is to stop building programs. Send the database a program template and a list of typed values. The template is the part you wrote; the values are the part the user controls. The driver and the database cooperate to keep them separate.

Data crosses into code at this line
Mini Map
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Try it

Break a query with one character

Edit the username or password. Watch the query assemble live. Add a single apostrophe and the database refuses to parse it - the application crashes. Add the canonical bypass (' OR '1'='1' --) and the WHERE clause disappears.

prodacme-portal.app/auth/login
acme-portal
Try a preset
Query the database will execute
SELECT * FROM users WHERE username = 'alice'' AND password = 'hunter2'
Press Send to server. The query above is what the application actually runs against the database.
Real-world relevance

Why every modern framework still leaks

The pattern persists because the safe path requires the developer to recognise every entry point as a potential injection site. ORMs default to safe queries, but every ORM ships a raw method (raw(), extra(), whereRaw(), exec()) that the developer reaches for when the ORM cannot express the query. Search fields, sort columns, JSON path filters, and dynamic WHERE clauses are the usual suspects. The static analysis catches most cases, but the long tail of bespoke business logic is where the breaches live.

Mitigation

What "safe" looks like

javascriptparameterised
// SAFE - bound parameter
const query = 'SELECT * FROM users WHERE username = $1 AND password = $2';
const result = await db.query(query, [username, password]);

Two changes matter. The query is now a single string literal that contains no user data. The user values travel as a separate array of typed parameters. The driver and the database know that the values are values; the parser will never treat them as code.

Further reading
  • SQL Injection (PortSwigger Web Security Academy)(PortSwigger)
  • CWE-89: Improper Neutralization of Special Elements used in an SQL Command(MITRE)
  • Why ORMs aren't enough(Pinckney Hugo Pro)
Key takeaways

What to remember

  • The vulnerability is a single line that concatenates user input into a SQL string.
  • One apostrophe is enough to break the grammar - that break is the proof the application is parsing attacker input as code.
  • The defence is to keep code and data separate on the wire: send a template, then send values.
  • ORMs default to safe but leak through raw query methods - every ORM has one.

Knowledge check

0/3 answered · 0 correct
  1. 1.A developer writes db.query("SELECT * FROM users WHERE id = " + req.query.id). Why is this vulnerable?

  2. 2.What does it mean for a value to "cross a trust boundary"?

  3. 3.Why does adding a single apostrophe cause a "syntax error" in the application?