Free — no signup required

A03: Injection (The SQL Nightmare)

4 min read

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.

Interview Tip

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.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free