Skip to content

CWE-115: Misinterpretation of Input

Overview

Misinterpretation of input occurs when an application or system component reads the structure or meaning of incoming data incorrectly because of ambiguous encoding, conflicting parsers, or inconsistent validation logic. It usually appears where several layers - web server, application framework, backend service - each parse the same input and disagree: an attacker crafts a payload that passes the security check in one layer and is interpreted as something else by the layer that acts on it. Common forms are HTTP request smuggling, SQL injection through character-encoding differences, and path traversal through URL interpretation inconsistencies.

Relationship to Other CWEs

This page's MITRE parent has no page here, so the bullets below reach the corpus sideways rather than up the tree. None of them is a relationship MITRE records; each is a page a reader holding a CWE-115 finding could plausibly be looking for instead.

  • CWE-115 (this page) - two components each read the same input correctly by their own rules and reach different conclusions about what it says, so a check passes in one layer and another layer acts on a different meaning
  • CWE-436 (Interpretation Conflict) - the Class this page sits under, covering any such disagreement between components that are individually behaving correctly. No page here
  • CWE-113 (Improper Neutralization of CRLF Sequences in HTTP Headers) - the one sibling under CWE-436 this corpus covers, and the worked example of the pattern: a proxy and an origin server disagree about where one HTTP message ends and the next begins
  • CWE-93 (Improper Neutralization of CRLF Sequences) - CWE-113's parent, for the same injected control characters outside an HTTP header
  • CWE-20 (Improper Input Validation) - the entry when a single component validated the input badly. The line between the two is whether any component is wrong on its own terms: if each parser is correct and only their readings differ, that is this page

OWASP Classification

A05:2025 - Injection

Risk

Medium to High: Misinterpretation vulnerabilities enable request smuggling, which leads to cache poisoning, credential hijacking, and firewall bypass. They also enable injection when validation logic and execution logic interpret input differently: a malicious payload evades the check and is still executed.

Remediation Steps

Core Principle: Make every layer interpret input identically: use the same parsing logic, declare encodings explicitly, and validate the form of the data that will actually be interpreted.

Trace Where Interpretations Can Diverge

Identify every place the same input is parsed or decoded more than once:

  • Source: Untrusted input entering any layer that parses it - HTTP request line/headers/body, file paths, serialized data, uploaded files
  • Sink: The layer whose interpretation actually determines behavior (the OS for a file path, the database for a query, the backend server for a proxied request)
  • Data Flow / Missing Controls: Look for a validation step and an execution step that each parse, decode, or normalize the same value independently - if they can disagree, an attacker can craft input that passes validation under one interpretation and executes under another

Standardize Input Parsing

Settle each of these decisions once and apply the same one in every layer:

  • Character encoding: Declare UTF-8 explicitly everywhere and reject ambiguous encodings
  • URL parsing: Use the same URL parser for validation and routing
  • Header interpretation: Handle multi-line headers, folding, and duplicate headers consistently
  • Content-Type processing: Validate Content-Type strictly, against what the parser will actually do

Normalize Input Before Validation

Canonicalize before running security checks:

# VULNERABLE - validation uses different interpretation than execution
user_path = "%252e%252e%252fetc/passwd"        # no ".." in the string as supplied
if is_safe_path(user_path):                    # passes - there is nothing to reject yet
    content = read_file(normalize(user_path))  # normalize() percent-decodes twice, giving
                                               # "../etc/passwd", which resolves outside
                                               # the intended directory

# SECURE - normalize first, then validate
normalized_path = normalize(user_path)
if is_safe_path(normalized_path):
    content = read_file(normalized_path)

Prevent HTTP Request Smuggling

The front end and the back end disagree on where one request ends and the next begins. To prevent it:

  • Use HTTP/2 end-to-end. HTTP/2 on its own does not close this off: downgrading HTTP/2 to HTTP/1.1 at the front end reintroduces desync as H2.CL and H2.TE. If downgrading is unavoidable, the front end must validate and rewrite Content-Length against the frame length it actually received, and reject Transfer-Encoding rather than pass it through
  • Make both ends agree on where a request ends. Desync is not caused by connection reuse; it is caused by the front end and the back end computing different body lengths from the same bytes. Run the same parser, or at least the same product and version, across the chain (nginx, HAProxy, the application server), and re-test the chain after any one of them is upgraded
  • Reject ambiguous framing rather than resolving it. A request carrying both Content-Length and Transfer-Encoding, several Content-Length fields whose values differ, a Transfer-Encoding where chunked is not the final coding, or an obfuscated Transfer-Encoding (xchunked, whitespace before the colon, a tab or space around the value, the header sent twice) should be answered with 400. RFC 9112 permits a server either to reject such a request or to honor Transfer-Encoding alone; rejecting is the option that does not depend on the next hop having made the same choice
  • Normalize before forwarding, if you forward at all. An intermediary that does pass a Content-Length + Transfer-Encoding request through must strip Content-Length and re-emit the body from the decoded Transfer-Encoding, so the back end never sees a second framing to disagree about. Forwarding both headers unchanged is what hands it the ambiguity
  • Close the connection instead of reusing it after any framing error. RFC 9112 requires this after responding to a both-headers request, and the reason generalizes to every parse failure: a socket that has just been mis-parsed holds bytes that the next request on it inherits as a prefix. Respond, then close - do not return the connection to the keep-alive pool
  • Disabling HTTP request pipelining is not a fix for this. It reduces how many requests share a connection, which narrows the window, but the two ends still disagree about boundaries and a smuggled prefix still lands on whatever request comes next over the same keep-alive connection. Treat it as a connection-reuse reduction, not as the remediation

Address Encoding Ambiguities

Validate the same bytes, decoded the same way, that will later be displayed:

# VULNERABLE - validation checks UTF-8, execution uses ISO-8859-1
if contains_xss(input_as_utf8):
    reject()
display(input_as_iso88591)  # Displays decoded differently!

# SECURE - force consistent encoding, then validate the same bytes that are displayed
normalized = force_utf8_encoding(input)
if contains_xss(normalized):
    reject()
display(normalized)

Validate After Final Interpretation

Perform security checks on the exact data form that will be processed:

  • If the database expects UTF-8, validate the UTF-8 form, not the raw bytes
  • If the OS expects a filesystem path, validate the canonical path, not the raw input
  • If the SQL engine interprets Unicode, validate the Unicode form, not the ASCII one

Use Strict Content-Type Validation

# VULNERABLE - prefix match accepts variants the parser then handles differently
if content_type.starts_with("application/json"):
    data = parse_json(request.body)

# SECURE - compare the parsed media type, not the raw header string
media_type, parameters = parse_media_type(content_type)
if lowercase(media_type) != "application/json" or has_unrecognized(parameters):
    reject()
data = parse_json(request.body)

The defect in the vulnerable line is the prefix match, not a missing parameter: application/json is also a prefix of application/json-seq and application/jsonrequest, both of which parse differently. Compare the media type on its own, lowercased - media types are case-insensitive, so Application/JSON is the same type - and reject parameters you do not recognize. Do not require charset=utf-8: RFC 8259 defines no charset parameter for application/json, so a check that demands one rejects conforming clients and accepts only a non-standard variant.

Implement Input Rejection

Reject these rather than choosing an interpretation for them:

  • Multiple encodings specified (UTF-8 and ISO-8859-1)
  • Both Content-Length and Transfer-Encoding headers
  • Conflicting URL path interpretations (/../, /..;/, /%2e%2e/)
  • Non-canonical representations (overlong UTF-8, mixed encodings)

Test for Misinterpretation

Verify the fix with inputs that exploit parser disagreement:

  • Double interpretation: Submit path traversal payloads that only appear after normalization (....//, %252e%252e%252f) and confirm validation runs on the normalized form
  • Conflicting headers: Send requests with both Content-Length and Transfer-Encoding set and confirm the server answers 400 rather than guessing, and that it closes the connection - a 400 returned on a connection that stays open leaves the smuggled bytes queued for the next request
  • Encoding mismatches: Submit non-UTF-8 byte sequences and overlong UTF-8 encodings and confirm they are rejected, not silently reinterpreted
  • Content-Type variants: Submit a body with a near-match Content-Type (application/json-seq, application/jsonrequest, an unrecognized parameter) and confirm it is rejected rather than parsed leniently, and confirm a differently-cased Application/JSON is still accepted
  • Re-scan with the security scanner, and if request smuggling is in scope, retest through the actual proxy chain rather than a single server

Additional Resources