OWASP Top 10 Deep Dive · 1 of 8

Injection — When Input Becomes Code

Injection happens when untrusted input is concatenated into a query, command, or expression that the system then executes. SQL injection is the famous one, but the family covers OS command injection, LDAP, XPath, NoSQL queries, ORMs misused, and template engines. The fix is a single discipline applied everywhere: data and code never share a string.

SQLiCommand InjectionNoSQLLDAPTemplate
← Back to Security
Quick Facts

What Injection Is

Basic Concepts

  • The pattern: the program builds a string ("SELECT * FROM users WHERE name='" + name + "'") and hands it to an interpreter. If name contains the interpreter's syntax, the meaning of the statement changes.
  • Interpreters everywhere: SQL, shell, eval, regex, XPath, LDAP, Mongo query operators, template engines, expression languages (Spring SpEL, OGNL).
  • Severity: typically critical. Successful injection often means full database read, write, deletion, or remote code execution.
  • Why it persists: string concatenation is the most natural thing for a developer to write. Frameworks have to make the safe path easier than the unsafe one.
The Family

The Common Injection Types

SQL Injection (SQLi)

The classic. Attacker input ' OR '1'='1 turns a login query into one that matches every row.

// Vulnerable
String q = "SELECT * FROM users WHERE name='" + name + "'";

// Safe — parameterized
PreparedStatement ps = c.prepareStatement(
    "SELECT * FROM users WHERE name = ?");
ps.setString(1, name);

Variants: classic (error-based or UNION-based), blind (no error output, infer via boolean or timing), second-order (input stored safely but later concatenated into a query).

OS Command Injection

Calling out to the shell with user input lets attackers chain commands with ;, &&, |, or backticks.

Fix: never use shell. Use the argv form of process spawn (execve, subprocess.run([...], shell=False), ProcessBuilder) so arguments stay arguments. If you must use a shell, escape using purpose-built libraries — and assume someone will eventually trick the escape.

NoSQL Injection

MongoDB query operators are JSON. If you accept a JSON body and pass it directly into a query, an attacker sends {"$gt": ""} instead of a string and matches any record.

Fix: validate and coerce input shapes. Treat user-supplied JSON as untrusted; don't pass it to a query builder unchecked.

LDAP, XPath, XML, Header Injection
  • LDAP: (uid=*)(uid=* bypasses auth filters. Use parameterized LDAP libraries.
  • XPath: same shape as SQLi but on XML documents. Use parameterized XPath APIs.
  • XML External Entity (XXE): a different but related class — disable external entity resolution in the XML parser.
  • HTTP header / response splitting: CRLF in user input breaks out of one header into a second one. Strip or reject newlines on anything that lands in a header.
Template Injection (SSTI)

Server-Side Template Injection: passing user input as a template string instead of as data lets attackers run template-engine expressions — often a path to remote code execution. Affected: Jinja2, Twig, Freemarker, Velocity, Razor when used with user-controlled templates.

Fix: never compile a template from user input. Templates are code; data goes in via the rendering context.

ORM & Query-Builder Pitfalls

ORMs are mostly safe, until someone uses raw queries with string interpolation. db.query(f"SELECT * FROM users WHERE name = '{name}'") in SQLAlchemy or @Query("... WHERE name = '" + name + "'") in Spring Data is just SQLi with extra steps.

Fix: always pass parameters separately (db.query("SELECT ... WHERE name = :name", {"name": name})), and lint for raw-string SQL in CI.

Defenses

How to Stop Injection

1. Parameterize Everything

Use parameterized queries / prepared statements / bind variables. Every database driver supports this. The interpreter receives the query template and the values separately and never confuses them. This is the single most effective defense ever invented for SQLi.

2. Validate & Allow-list

Parameterization fixes data being interpreted as code. It doesn't help when the user-supplied value is meant to be code — a sort column name, a table name, an enum. For those, use an allow-list: compare the input against a fixed set of accepted values and reject anything else.

3. Least-Privilege Database Accounts

If the app's DB user can only SELECT from its own schema, even successful SQLi can't drop the table or read another tenant's data. Production app accounts should never have DROP, CREATE, or cross-schema read.

4. Avoid Shell Calls Entirely

Use language-native libraries instead of shelling out. If you absolutely must, use the argv form and assume any escaping you write will eventually fail.

5. Static Analysis & Code Review

Tools like Semgrep, SonarQube, CodeQL, and Snyk Code flag string-concatenated SQL and shell calls. Add them to CI. Most injection bugs are caught by a tool before they reach a human reviewer.

6. Web Application Firewalls (Defense, Not Cure)

A WAF (Cloudflare, AWS WAF, modsecurity) blocks obvious payloads. Useful as a tripwire and to slow drive-by scanners. Don't rely on it as the primary defense — every WAF has bypasses, and the moment your app trusts the WAF to filter input, your code stops being safe.

Continue

Other OWASP Top 10