Input Validation & Injection
In a Nutshell
Injection attacks happen when untrusted input is interpreted as code or commands instead of mere data — the attacker sneaks malicious instructions into a field, and the system dutifully executes them. SQL injection is the classic: a login form that builds a query by concatenating the username lets an attacker turn ' OR '1'='1 into a query that bypasses authentication or dumps the whole database. The defense is input validation (checking that input matches expectations) combined with proper handling that keeps data and code strictly separated — above all, parameterized queries. Injection has topped the OWASP risk list for years because it's common, easy to exploit, and devastating.

How It Actually Works
The Root Cause: Mixing Data and Code
Every injection vulnerability shares one root cause — untrusted input crosses the boundary from data into an interpreter (SQL engine, shell, HTML parser) that treats part of it as instructions:
❌ VULNERABLE — building a query by string concatenation:
query = "SELECT * FROM users WHERE name = '" + input + "'"
Attacker inputs: ' OR '1'='1' --
Resulting query: SELECT * FROM users WHERE name = '' OR '1'='1' --'
→ '1'='1' is always true → returns ALL users. 💥
Worse: '; DROP TABLE users; -- → destroys data.
Types of Injection
| Type | Interpreter Abused | Example |
|---|---|---|
| SQL injection | Database query engine | ' OR 1=1 -- |
| NoSQL injection | NoSQL query (e.g., Mongo) | {"$gt": ""} operators |
| Command injection | OS shell | ; rm -rf / in a filename |
| LDAP injection | Directory queries | Filter manipulation |
| XPath/XML injection | XML parsers | Query/entity manipulation |
| Template injection | Server-side templates | {{7*7}} executing code |
| XSS | Browser (HTML/JS) | Script in a page (see XSS & CSRF) |
The Primary Defense: Parameterized Queries
The single most important fix for SQL injection: never build queries by concatenation. Use parameterized queries / prepared statements, which send the query structure and the data separately so the data can never be interpreted as SQL:
# ❌ VULNERABLE
cursor.execute("SELECT * FROM users WHERE name = '" + name + "'")
# ✅ SAFE — parameterized: the DB treats `name` strictly as a value
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
With a bound parameter, ' OR '1'='1 is looked up as a literal (nonexistent) username — it can never change the query's meaning. ORMs and query builders do this by default, which is a major reason to use them.
Input Validation: Defense in Depth
Validation complements (but does not replace) safe interfaces like parameterized queries:
| Approach | Rule |
|---|---|
| Allowlist (positive) | Accept only known-good patterns (e.g., "must be a 5-digit ZIP") — preferred |
| Denylist (negative) | Block known-bad patterns — weak (attackers find bypasses) |
| Type/format/range checks | Enforce expected type, length, format, bounds |
| Canonicalization | Normalize input before checking (avoid encoding tricks) |
Allowlisting is far stronger than denylisting — defining what's valid is finite and reliable; enumerating everything malicious is a losing game.
The Layered Defenses Against Injection
1. Parameterized queries / prepared statements ← primary (data ≠ code)
2. Input validation (allowlist) ← reject bad input early
3. Least-privilege DB accounts ← app can't DROP tables
4. Escaping/encoding for the specific context ← when parameterization
isn't possible
5. ORMs / safe APIs ← safe by default
6. WAF ← catches known patterns
No single layer is sufficient; injection defense is defense in depth. Even with parameterized queries, run the app's DB account with least privilege (see Authentication & Authorization) so a missed spot can't drop tables.
Output Encoding (the XSS Cousin)
For interpreters like the browser, the equivalent defense is context-aware output encoding — encoding data so it renders as text, not markup/script. Validation guards input; encoding guards output. Both are needed (detailed in XSS & CSRF).

Seeing It in Action
Scenario: Securing a search feature against SQL injection.
Vulnerable version (do NOT do this):
q = request.args["q"]
sql = f"SELECT id, title FROM articles WHERE title LIKE '%{q}%'"
db.execute(sql) # attacker q = "%'; DROP TABLE articles; --"
Secured version — layered defenses:
1. Validate (allowlist): search terms are letters/numbers/spaces, ≤ 100 chars
if not re.fullmatch(r"[\w\s\-]{0,100}", q): return 400
2. Parameterize (primary defense): bind q as DATA, never concatenate
db.execute(
"SELECT id, title FROM articles WHERE title LIKE %s",
(f"%{q}%",)) # the DB treats q strictly as a value
3. Least-privilege DB account: the web app connects as a role with
SELECT/INSERT/UPDATE on app tables only — NO DROP, NO DDL, no access
to other schemas. Even a hypothetical injection can't drop a table.
4. ORM alternative (safe by default):
Article.objects.filter(title__icontains=q) # parameterized internally
What each layer stops:
- Parameterization → the core fix: ' OR 1=1, '; DROP..., UNION SELECT...
are all treated as literal search text, not SQL. Neutralized.
- Validation → rejects obviously malicious/oversized input before the DB.
- Least privilege → if some OTHER query is ever vulnerable, the blast
radius is capped (can't drop/alter, can't reach other data).
Why the layering is the real lesson: parameterized queries alone actually solve SQL injection for that query — so why the other layers? Because real systems are large and imperfect: a developer somewhere will build a query by concatenation, a new endpoint will forget validation, or a different injection type (command, NoSQL) will slip through. Defense in depth means a single mistake doesn't become a catastrophe. Input validation rejects malicious input early and cheaply; parameterization removes the data-as-code danger for the query you're writing now; and least-privilege database accounts ensure that even if an injection is found somewhere, the attacker can't drop tables, escalate, or reach data the app doesn't need. The mindset that prevents injection is treating all input as untrusted and never letting it cross into an interpreter as code — enforced by safe-by-default tools (ORMs, parameterization) plus validation, so the secure path is also the easy path.
Interview Questions
Q: What is the root cause of injection attacks? Hint: Untrusted input crossing the boundary from data into an interpreter (SQL engine, OS shell, HTML/JS parser, template engine) that treats part of it as code/commands rather than mere data. Whenever input is concatenated into something that gets interpreted, an attacker can inject instructions. The fix is keeping data and code strictly separated so input is always treated as inert data.
Q: How do parameterized queries prevent SQL injection? Hint: They send the query structure and the data to the database separately — the SQL text has placeholders, and the values are bound as parameters. The database treats bound values strictly as data, never parsing them as SQL, so input like
' OR '1'='1is looked up as a literal (nonexistent) value and can't change the query's meaning. This eliminates the concatenation that causes injection.Q: Why is allowlist validation preferred over denylist? Hint: Allowlisting defines exactly what's valid (e.g., "5 digits", "letters and spaces, ≤100 chars") — a finite, reliable specification that rejects everything else. Denylisting tries to enumerate all malicious patterns, which is an endless, losing game: attackers find encodings, edge cases, and new payloads that bypass the blocklist. Define what's good, not what's bad.
Q: If parameterized queries fix SQL injection, why also use least-privilege DB accounts and input validation? Hint: Defense in depth. Real systems are large and imperfect — some query will be built unsafely, a new endpoint will miss validation, or another injection type appears. Input validation rejects bad input early; least-privilege DB accounts cap the blast radius (the app can't DROP tables or reach other data) if any injection slips through. No single layer is trusted to be perfect everywhere.
Q: How does defending against XSS relate to defending against SQL injection? Hint: Same root cause (data interpreted as code), different interpreter. SQLi abuses the database query engine — defended with parameterized queries. XSS abuses the browser's HTML/JS parser — defended with context-aware output encoding (rendering data as text, not markup). Both also benefit from input validation. The unifying principle: never let untrusted input cross into an interpreter as executable content.
References
- OWASP: SQL Injection — attack and prevention
- OWASP Injection Prevention Cheat Sheet — practical defenses
- OWASP Input Validation Cheat Sheet — allowlisting and validation
Dive Deeper
- Bobby Tables (xkcd) & the guide to preventing SQL injection — examples in every language
- OWASP Top 10: A03 Injection — the risk in context
- PortSwigger Web Security Academy: SQL injection — hands-on labs