What Injection Really Means
Imagine you hand a note to a bank teller that says "Give me my balance." That is a normal request. Now imagine you hand a note that says "Give me my balance AND give me everyone else's balance too." If the teller follows the second instruction without questioning it, that is injection — the teller (the interpreter) cannot distinguish between your legitimate request and the extra malicious instruction you smuggled in.
In software, injection occurs when untrusted user-supplied data is sent to an interpreter — a SQL database engine, a shell, an LDAP server, an XML parser — as part of a command or query, and the interpreter executes the data as if it were code. The root cause is always the same: the application fails to separate data from instructions.
SQL Injection: The Classic Attack
Consider this vulnerable code:
# VULNERABLE: String concatenation builds the query
username = request.form['username']
query = "SELECT * FROM users WHERE username = '" + username + "'"
db.execute(query)
A normal user enters alice. The query becomes:
SELECT * FROM users WHERE username = 'alice'
An attacker enters ' OR '1'='1. The query becomes:
SELECT * FROM users WHERE username = '' OR '1'='1'
Since '1'='1' is always true, this returns every row in the users table. The attacker has bypassed authentication entirely. More destructive payloads can delete tables, exfiltrate data, or — on misconfigured databases — execute operating system commands.
# Attacker input that drops the entire users table (classic "Bobby Tables" attack)
'; DROP TABLE users; --
The -- at the end is a SQL comment character. It causes the database to ignore everything after it, preventing syntax errors that would reveal the attack.
The Fix: Parameterized Queries (Prepared Statements)
The correct fix is to never concatenate user input into a query string. Instead, use parameterized queries, where the query structure is defined first and user data is passed separately. The database engine then treats the data strictly as a value — it cannot be interpreted as SQL syntax.
# SECURE: Parameterized query
username = request.form['username']
query = "SELECT * FROM users WHERE username = ?"
db.execute(query, (username,)) # username is passed as a parameter, not concatenated
// Java equivalent using PreparedStatement
String query = "SELECT * FROM users WHERE username = ?";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, username); // The DB treats this strictly as a string value
ResultSet rs = stmt.executeQuery();
If the attacker now enters ' OR '1'='1, the database literally searches for a user whose username is the string ' OR '1'='1 — which doesn't exist — and returns nothing. The attack is completely neutralized.
Beyond SQL: Other Injection Variants
Injection is not limited to SQL. The same principle applies wherever user data reaches an interpreter:
| Injection Type | Interpreter | Example Payload |
|---|---|---|
| OS Command Injection | Shell | ; rm -rf / |
| LDAP Injection | Directory server | *)(uid=*))(|(uid=* |
| XML/XPath Injection | XML parser | ' or '1'='1 |
| Template Injection | Template engine (Jinja2, Twig) | {{7*7}} → renders 49 |
Server-Side Template Injection (SSTI) deserves special mention: if a template engine evaluates user input as a template expression, attackers can often achieve Remote Code Execution (RCE) — the most severe possible outcome.
Interviewers often ask: "What's the difference between input validation and parameterized queries for preventing SQL injection?" The correct answer is that parameterized queries are the primary defense — they structurally prevent injection by separating code from data. Input validation (whitelisting, rejecting special characters) is a defense-in-depth measure, not a substitute. A regex that blocks ' characters can often be bypassed with encoding tricks; a parameterized query cannot.
Key Point: The underlying principle behind all injection defenses is the same — enforce a strict boundary between code and data. Parameterized queries do this at the database layer. Output encoding does this at the rendering layer. The moment you concatenate untrusted input into any interpreted string, you have created an injection surface.