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.
← Back to SecuritySELECT * FROM users WHERE name='" + name + "'") and hands it to an interpreter. If name contains the interpreter's syntax, the meaning of the statement changes.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).
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.
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.
(uid=*)(uid=* bypasses auth filters. Use parameterized LDAP libraries.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.
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.
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.
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.
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.
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.
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.
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.