Skip to content
Language:en Author:Laborci Gergely
web-security

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-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:

  1. Parameterized query (prepared statement).
  2. Stored procedures, if they were made safely (with parameters, not with dynamic SQL).
  3. 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.
  4. 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.
"An ORM (Object-Relational Mapping) protects automatically." Only if it uses a parameterized query; not with raw SQL concatenation.
"Input validation is enough." It is useful for business rules, but cannot replace parameterization.
"Injection exists only with SQL." It can occur with any interpreter.
"HTTPS protects against injection." The attacker's data reaches the server over an encrypted channel too (4.3).

In brief

  • Injection is the mixing of code and data: the input is interpreted as a command.
  • The tried concatenated SQL gave out the internal course for the attacker's input; the parameterized query did not.
  • Main defense: parameterized query; an allow-list on the parts that cannot be parameterized; escaping must not be the solution.
  • Supplement: least privilege on the database account.

Review questions

  1. Why did the input x' OR '1'='1 make the internal course visible?
  2. How is a parameterized query different from a concatenated one?
  3. Why is deleting or escaping special characters not a good solution?
  4. When must an allow-list be used instead of parameterization?

Learning resources

Prerequisites (1)

  • HTTP

Teaches (2)

  • injection
  • parameterized query