A05 · Injection
Program: Application Security — OWASP Top 10 and Threat Modeling Module: OWASP Top 10 — Web Application Security Risks Submodule: A05:2025 · Injection
Injection was #1 for over a decade. In 2021 it dropped to #3 as modern ORMs and parameterized query APIs became defaults; XSS was merged into it. In 2025 it drops further to #5 — still critical, still ubiquitous, but no longer the industry's top weakness. Maps to STRIDE's Tampering (of the interpreter's input) and often Elevation of Privilege (via the interpreter's rights).
1. What It Is
An injection is any bug where attacker-controlled data is passed to an interpreter as part of a command or query, and the interpreter treats data as code. The pattern is identical across many interpreters:
- SQL Injection (SQLi) — into a database engine.
- NoSQL Injection — into MongoDB, Elasticsearch, Redis, DynamoDB.
- OS Command Injection — into
sh,cmd.exe,python -c. - LDAP Injection — into directory servers.
- XPath / XQuery Injection — into XML query engines.
- ORM Injection — into an ORM's query DSL (raw fragments).
- Template Injection (SSTI) — into Jinja2, Twig, Freemarker, ERB.
- XSS (Cross-Site Scripting) — into a browser JS interpreter; merged into Injection since 2021.
- Reflected XSS, Stored/Persistent XSS, DOM XSS.
- CRLF injection — into log lines, HTTP headers, email headers.
- Log injection — untrusted input written raw to a log then displayed in a viewer.
- NoSQL / GraphQL query injection and prompt injection for LLM-backed apps are the newer siblings.
2. How It Happens
Every injection has the same shape:
untrusted input → string-concatenated → interpreter that parses codeClassic SQLi:
python
# ❌ Broken
cursor.execute("SELECT * FROM users WHERE id = " + request.args["id"])
# ✅ Fixed — parameters, not concatenation
cursor.execute("SELECT * FROM users WHERE id = %s", (request.args["id"],))Classic reflected XSS:
html
<!-- ❌ Broken: template writes user input into HTML unescaped -->
<h1>Hello {{ raw name }}</h1>
<!-- ✅ Fixed: default auto-escape -->
<h1>Hello {{ name }}</h1>Command injection:
python
# ❌ Broken: shell parses the string
os.system("convert " + filename + " out.png")
# ✅ Fixed: argv list, no shell
subprocess.run(["convert", filename, "out.png"], check=True)Prompt injection (2024+):
# ❌ Broken: user text is dropped into the system prompt
system = "You are a helpful bot. USER SAID: " + user_input
# ✅ Mitigated: separate roles, output constraints, tool allow-list,
# never trust LLM-produced tool arguments without re-validation.3. Prevention — One Pattern per Interpreter
| Interpreter | The correct pattern |
|---|---|
| SQL | Parameterized queries / prepared statements. Never string-concatenate. Use ORM query builders for column/table names, allow-listed. |
| NoSQL | Native driver operators; type-check inputs (e.g. don't accept a document where a string is expected). |
| OS command | Pass argv as a list; no shell (shell=False in Python). Prefer library APIs over shelling out. |
| LDAP | LDAP-encode inputs (ldap.filter.escape_filter_chars); allow-list DN components. |
| XPath / XQuery | Parameterized queries in modern libraries. |
| Template engines | Auto-escaping on by default; never render user input into a template string, only into a template variable. |
| HTML (XSS) | Context-aware output encoding: HTML, HTML attribute, JS, CSS, URL. Frameworks (React, Vue, Svelte) escape by default — don't defeat them with dangerouslySetInnerHTML. Add a strong Content-Security-Policy. |
| HTTP headers | Reject \r\n in any header value; use framework APIs that do this. |
| LLM prompts | Structural separation of instructions and data; output constraints (JSON schemas); minimal tool permissions; treat all LLM output as untrusted before it hits any interpreter. |
Plus universal practices:
- Input validation as defense in depth (never as the primary defense): allow-list character sets, lengths, structural formats.
- Least privilege on the interpreter — DB user with only the rights it needs; command-line binaries in a sandbox; templates in a sandboxed mode.
- WAF as detection & speed bump, never as primary protection.
4. Detection & Testing
- SAST — very effective for injection classes because the dangerous sinks are enumerable. Semgrep, CodeQL, SonarQube.
- DAST — Burp, ZAP, Nuclei; targeted payload libraries for SQLi (
sqlmap), XSS, template injection, SSRF. - Framework-level testing — verify template auto-escape is on; verify ORM raw-query linters (
bandit,codeql,semgrep). - Interactive AppSec Testing (IAST) — agent inside the app links taints from source to sink at runtime; low false positives.
- CSP report-only mode — catches XSS payloads that make it into production without breaking the site.
- Log the payload category, not the payload — logging attacker payloads verbatim can create secondary log-injection issues.
5. Key Takeaways
- Every injection has the same shape: untrusted data → concatenation → interpreter. Fix by using the interpreter's parameterized API.
- XSS is now part of Injection (since 2021) — the fix is context-aware output encoding, and frameworks that do this by default.
- Injection is easily catchable by SAST — this is one of the few Top 10 categories where "buy a scanner and enforce it in CI" is most of the answer.
- Prompt injection is the newest sibling, and unlike classical injection there is no known parameterization primitive — you have to design the system to never let LLM output act with your privileges.
6. Glossary (this submodule)
- Injection — bug class where attacker-controlled data is interpreted as code by a downstream interpreter.
- Parameterization / Prepared Statement — mechanism for supplying values to a query separately from the query text, so values are never parsed as code.
- XSS (Cross-Site Scripting) — injection into a browser's JS interpreter through the HTML/JS/CSS/URL contexts.
- SSTI (Server-Side Template Injection) — injection into a server template engine (Jinja2, Twig, Freemarker) that grants arbitrary code execution.
- CSP (Content Security Policy) — HTTP header that restricts which sources of script, style, frame, etc. a browser will honor; defense-in-depth against XSS.
- WAF — Web Application Firewall; pattern-matching filter in front of an app. Useful for detection and rate-limiting; poor as a primary control.
- Prompt injection — injection variant targeting LLMs; malicious input in a data field manipulates model instructions or downstream tool calls.
7. What's Next
- A06 · Insecure Design — many injection bugs are also design bugs (the interpreter didn't need to be user-reachable at all).
- Cross-refs: A01 · Broken Access Control (a successful injection often escalates privilege), A03 · Software Supply Chain Failures (a compromised dep may inject the sink you were worried about).