CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
Overview
CWE-74 is the broad parent category for the entire injection family: a product builds a command, query, document, or other structured output from externally-influenced input, but fails to neutralize elements that a downstream interpreter (a database, shell, LDAP server, XML parser, browser, etc.) would treat as syntax rather than data. MITRE marks CWE-74 as discouraged for direct vulnerability mapping because it is too abstract to act on. Almost every real finding is one of its more specific child weaknesses, and that is where the usable guidance lives.
Relationship to Other CWEs
Treat this page as a router, not a fix. If a scanner or manual finding names (or clearly matches) one of the following, use its dedicated page instead - the primary defense differs by sink type:
- CWE-74 (this page) - the injection family as a whole: input that a downstream interpreter reads as syntax rather than as data. Too abstract to map a finding to.
- CWE-77 - Command Injection (any command language, not only the OS shell; its children include CWE-78 (OS Command Injection), CWE-88 (Argument Injection), and CWE-1427 (LLM Prompt Injection))
- CWE-79 - Cross-Site Scripting (script injected into web page output)
- CWE-89 / CWE-90 - SQL / LDAP query injection
- CWE-91 - XML Injection
- CWE-93 - CRLF Injection
- CWE-94 - Code Injection (attacker-controlled code enters the interpreter/compiler itself)
- CWE-99 - Resource Injection (untrusted input selects a resource identifier - file, URL, connection string)
- CWE-943 - Data Query Logic Injection (NoSQL and similar query-language injection)
- CWE-1236 - CSV/Formula Injection
One neighbour is not a sink and so is not in that list. CWE-159 (Improper Handling of Invalid Use of Special Elements) covers special elements handled wrongly with no particular downstream interpreter in view. It is a cousin rather than a relative: it sits under CWE-138 (Improper Neutralization of Special Elements), which is this page's sibling Class under the CWE-707 (Improper Neutralization) pillar, and MITRE records no direct relationship between the two. Neither CWE-138 nor CWE-707 has a page here, so CWE-159 is the closest thing to an entry point for that branch.
Use this page only when a finding is reported generically as "Injection" with no more specific CWE, or as background on what the injection family has in common.
OWASP Classification
A05:2025 - Injection
Risk
Critical: Injection leads to unauthorized data access or modification, authentication bypass, or remote code execution, depending on the interpreter the untrusted data reaches. A shell gives an attacker command execution, a database gives them the data itself, a browser gives them the victim's session.
Remediation Steps
Core Principle: Never let untrusted input control the syntax of a command, query, or document sent to a downstream interpreter; use an API that keeps user data as data, so the interpreter cannot reinterpret it as instructions.
Trace the Data Path
- Source: Where untrusted input enters (HTTP parameters, headers, cookies, file uploads, upstream API responses).
- Sink: The interpreter that receives the tainted data (database driver, shell, LDAP client, XML parser, browser DOM, template engine).
- Data Flow / Missing Controls: String concatenation, string formatting, or template interpolation that builds the command/query/document from untrusted parts, with no parameterization or encoding step in between.
Use the Sink-Specific Structured API (Primary Defense)
There is no single "sanitize this" fix for injection in general - the correct primary defense depends on the sink:
- Databases: parameterized queries / prepared statements, never string-built SQL.
- OS commands: array/list-based process execution with the shell disabled, or better, a native library instead of a shell command at all.
- Browsers: context-aware output encoding, or a template engine that auto-escapes.
- LDAP, XML, and other structured formats: the platform's escaping function for that specific grammar, or a builder API that constructs the structure programmatically instead of by string concatenation.
Identify which injection type applies and follow that CWE's page, listed under Relationship to Other CWEs above, for the concrete API.
Add Input Validation (Defense in Depth)
Even with a structured API in place, validate untrusted data as a second layer:
- Integers parse as integers, and dates match an expected format.
- Enumerated values are checked against a known-good list.
- Excessively long input is rejected, which reduces parser and DoS risk.
Validation supplements the structured API; it does not replace it.
Apply Least Privilege
Limit the blast radius if an injection flaw is missed:
- Database accounts: minimum required permissions (no
DROP/ALTERfor an account that only needsSELECT). - OS processes: run with the least privilege the task requires, never as root/Administrator.
- API keys/tokens: scope to the specific operations they need.
Test with Malicious Input
- Send the sink's own metacharacters (
',;,|,<,`, depending on the interpreter) and confirm they are treated as literal data, not syntax. - Confirm legitimate input still works correctly after the fix.
- Re-scan with the security tool that reported the finding to confirm it is resolved.
Common Vulnerable Patterns
// VULNERABLE - pseudo-code
command = build_command(template, untrusted_input)
// untrusted_input is concatenated directly into the command/query/document string
downstream_interpreter.execute(command)
// Attack: untrusted_input contains syntax meaningful to the interpreter
// Result: the interpreter executes attacker-controlled instructions, not just data
Why this is vulnerable: the concatenation produces one string, and a string carries no record of where each character came from. The interpreter that receives it has no way to know that the first part was written by the developer and the rest arrived in a request - it parses the whole thing by its own grammar, so any character in the untrusted portion that happens to be syntax in that grammar is syntax.
That is why the whole family shares one fix design rather than an escaping rule per sink. Escaping keeps the single-string design and adds a rule the developer has to get right: the exact grammar of the downstream interpreter, applied at every call site, and still correct after something downstream decodes the value again. Parameterization, prepared statements, argument arrays and structured builders take a different approach. They hand the code and the data to the interpreter over separate channels, so the data has no opportunity to be read as syntax and there is no rule to remember. Where a genuine structured API exists for the sink, using it is not a stronger version of escaping; it is a different design in which the bug cannot be expressed.
Secure Patterns
// SECURE - pseudo-code
command = build_command(template, PLACEHOLDER)
downstream_interpreter.execute(command, parameters = [untrusted_input])
// untrusted_input is passed separately, never merged into the command/query text
Why this works: When untrusted data is passed as a separate parameter rather than merged into the command/query/document text, the interpreter parses the structure first and binds the parameter afterward as pure data. There is no point at which attacker-controlled characters are evaluated as syntax, so the class of attack is eliminated rather than filtered.