By the end of the lesson, you can explain the essence of injection (the mixing of code and data), you can read how an SQL injection works, and you know why the parameterized query is the main defense.
Why is a concatenated query dangerous?
The course search function of Kurzuspont queries the course from the database by its identifier. If the server concatenates the received identifier as text into an SQL query (SQL: Structured Query Language, the query language of relational databases), the input can become part of the query's syntax. The attacker then gives not data but a command wrapped as data. The essence of injection is therefore not the strange character, but the mixing of code and data. The same principle holds for other interpreters: a command shell, a template engine. In the OWASP Top 10:2025 list injection is category 5 (A05).
Tried: concatenated and parameterized query
A small database with three courses: two public (nyilvanos = 1, "public"), one internal, non-public. The search returns the public courses by identifier. We passed the same input to the SQLite database in two ways (Node.js 24, the built-in node:sqlite module):
// CONCATENATED (dangerous):const naiv = `SELECT id, cim FROM kurzus WHERE nyilvanos = 1 AND id = '${bemenet}'`;
// PARAMETERIZED (safe):const par = db.prepare('SELECT id, cim FROM kurzus WHERE nyilvanos = 1 AND id = ?');
par.all(bemenet);
(The identifiers are Hungarian: kurzus is "course", cim "title", bemenet "input", naiv "naive", par short for "parameterized".)
1. Normal input: webprog
SQL (concatenated): SELECT id, cim FROM kurzus WHERE nyilvanos = 1 AND id = 'webprog'
-> [{"id":"webprog","cim":"Webprogramozás 1."}]
SQL (parameterized): ... id = ? [parameter: "webprog"]
-> [{"id":"webprog","cim":"Webprogramozás 1."}]
Both versions work correctly.
2. Attacker input: x' OR '1'='1
SQL (concatenated): SELECT id, cim FROM kurzus WHERE nyilvanos = 1 AND id = 'x' OR '1'='1'
-> [{"id":"webprog",...},{"id":"adatbazisok",...},{"id":"belso","cim":"Belső oktatói értekezlet"}]
SQL (parameterized): ... id = ? [parameter: "x' OR '1'='1"]
-> []
(The title "Belső oktatói értekezlet" means "Internal instructors' meeting".)
In the concatenated version the quotation mark in the input closed the string, and OR '1'='1' added a new condition that is always true. Because AND binds more strongly than OR, the nyilvanos = 1 restriction became ineffective, and the internal meeting appeared too. In the parameterized version the whole input remained a single string value: there is no course with such an identifier, so the answer is empty.
3. A more targeted input: x' OR nyilvanos = 0 --
SQL (concatenated): SELECT id, cim FROM kurzus WHERE nyilvanos = 1 AND id = 'x' OR nyilvanos = 0 --'
-> [{"id":"belso","cim":"Belső oktatói értekezlet"}]
SQL (parameterized): ... id = ? [parameter: "x' OR nyilvanos = 0 --"]
-> []
In SQL, -- starts a comment, so the attacker made the rest of the query disappear as well. Here they specifically obtained the internal course.
flowchart LR
V[User value] --> P[Parameter]
Q[Fixed SQL structure] --> E[Database engine]
P --> E
E --> R[Value interpreted as data]
Why does parameterization work?
With a parameterized query (prepared statement) the SQL structure and the user value reach the database separately. The database first interprets the structure of the query (... id = ?), and the parameter takes its place only afterwards as a value, never as a command. According to OWASP's SQL injection guide this is the primary defense: the database treats the input as data, not as code.
The guide gives the following order:
Parameterized query (prepared statement).
Stored procedures, if they were made safely (with parameters, not with dynamic SQL).
Allow-list input validation where a parameter cannot be used (for example a table or column name, a sort direction): the user value must be mapped to known, good values.
Escaping characters: the guide strongly discourages this, because it is brittle and does not guarantee protection.
A supplementary defense is the principle of least privilege: the application's database account should be able to do only what it needs (it should not be an administrator account), and there should be a separate database user per application. This way a flaw causes less damage.
Other interpreters, the same idea
Injection arises when the program builds text that another interpreter reads as a command: an SQL engine, the command line of the operating system, a template engine, the query language of a search service. The defense is always the same principle: the structure of the command and the user value should reach the interpreter through separate channels, or the value must be handled safely according to the rules of the given interpreter. XSS (8.3) is a related idea: there the interpreter is the browser's HTML parser.
Worked example: the course search of Kurzuspont
Step
Decision
The search query
A parameterized query, the identifier enters only as a value.
Sort direction from the interface (ASC/DESC)
An allow-list: only these two values may enter the query.
The application's database account
Read-only on the kurzus table; no rights to the internal tables.
The internal course (nyilvanos = 0)
The rule is enforced by the query and the authorization layer (7.4).
This way even a faulty piece of code would not give out the internal courses.
Common misconceptions
Statement
Clarification
"It is enough to delete every special character."
Correct handling depends on the interpretation context; for SQL parameterization is needed.
Injection: when data becomes a command
Objectives
By the end of the lesson, you can explain the essence of injection (the mixing of code and data), you can read how an SQL injection works, and you know why the parameterized query is the main defense.
Why is a concatenated query dangerous?
The course search function of Kurzuspont queries the course from the database by its identifier. If the server concatenates the received identifier as text into an SQL query (SQL: Structured Query Language, the query language of relational databases), the input can become part of the query's syntax. The attacker then gives not data but a command wrapped as data. The essence of injection is therefore not the strange character, but the mixing of code and data. The same principle holds for other interpreters: a command shell, a template engine. In the OWASP Top 10:2025 list injection is category 5 (A05).
Tried: concatenated and parameterized query
A small database with three courses: two public (
nyilvanos = 1, "public"), one internal, non-public. The search returns the public courses by identifier. We passed the same input to the SQLite database in two ways (Node.js 24, the built-innode:sqlitemodule):// CONCATENATED (dangerous): const naiv = `SELECT id, cim FROM kurzus WHERE nyilvanos = 1 AND id = '${bemenet}'`; // PARAMETERIZED (safe): const par = db.prepare('SELECT id, cim FROM kurzus WHERE nyilvanos = 1 AND id = ?'); par.all(bemenet);(The identifiers are Hungarian:
kurzusis "course",cim"title",bemenet"input",naiv"naive",parshort for "parameterized".)1. Normal input:
webprogBoth versions work correctly.
2. Attacker input:
x' OR '1'='1(The title "Belső oktatói értekezlet" means "Internal instructors' meeting".)
In the concatenated version the quotation mark in the input closed the string, and
OR '1'='1'added a new condition that is always true. BecauseANDbinds more strongly thanOR, thenyilvanos = 1restriction became ineffective, and the internal meeting appeared too. In the parameterized version the whole input remained a single string value: there is no course with such an identifier, so the answer is empty.3. A more targeted input:
x' OR nyilvanos = 0 --In SQL,
--starts a comment, so the attacker made the rest of the query disappear as well. Here they specifically obtained the internal course.flowchart LR V[User value] --> P[Parameter] Q[Fixed SQL structure] --> E[Database engine] P --> E E --> R[Value interpreted as data]Why does parameterization work?
With a parameterized query (prepared statement) the SQL structure and the user value reach the database separately. The database first interprets the structure of the query (
... id = ?), and the parameter takes its place only afterwards as a value, never as a command. According to OWASP's SQL injection guide this is the primary defense: the database treats the input as data, not as code.The guide gives the following order:
A supplementary defense is the principle of least privilege: the application's database account should be able to do only what it needs (it should not be an administrator account), and there should be a separate database user per application. This way a flaw causes less damage.
Other interpreters, the same idea
Injection arises when the program builds text that another interpreter reads as a command: an SQL engine, the command line of the operating system, a template engine, the query language of a search service. The defense is always the same principle: the structure of the command and the user value should reach the interpreter through separate channels, or the value must be handled safely according to the rules of the given interpreter. XSS (8.3) is a related idea: there the interpreter is the browser's HTML parser.
Worked example: the course search of Kurzuspont
ASC/DESC)kurzustable; no rights to the internal tables.nyilvanos = 0)This way even a faulty piece of code would not give out the internal courses.
Common misconceptions
In brief
Review questions
x' OR '1'='1make the internal course visible?Learning resources