Safeguard
Application Security•

CWE-644 Scripting Syntax in HTTP Headers: Risk and Fix

Reflect user input into an HTTP response header without neutralizing it and clients that read raw headers can run attacker script. Encode output, in headers too.

Safeguard Security Team
4 min read

CWE-644, Improper Neutralization of HTTP Headers for Scripting Syntax, applies when a product does not neutralize web scripting syntax in HTTP headers that browser components, such as Flash, can process raw. It hits web servers and web applications that echo user-controlled data into response headers or into error messages built from request headers.

How it happens

Developers learn to encode data placed in HTML bodies. Headers get less attention, because browsers do not usually render them. The assumption is that a header is metadata and cannot execute.

MITRE explains why that is not always true. Some browser components can process raw headers. If user-controlled data ends up in a response header unneutralized, the header may carry a script that gets executed in the client's browser context. That can result in cross-site scripting, or possibly an HTTP response splitting attack.

A related path is the error page. A server takes part of the request, such as a header value, and reflects it into an error message. If the message is attacker-readable by a component that parses it, the reflected value becomes an XSS vector.

# Vulnerable: user input copied into a response header
@app.get("/redirect")
def go():
    resp = make_response("", 302)
    resp.headers["X-Origin"] = request.args.get("from", "")   # raw
    resp.headers["Location"] = "/home"
    return resp

The MITRE observed example is CVE-2006-3918: a web server did not remove the Expect header from an HTTP request when it was reflected back in an error message, allowing a Flash SWF file to perform XSS attacks.

That example shows the full shape. The attacker could make a victim's browser send a request with a crafted header through a browser plugin. The server echoed the header into its error output, and the result executed as script in the victim's session with the server's origin.

What an attacker gains

MITRE lists two consequences:

  • Executing unauthorized code or commands, affecting integrity, confidentiality and availability: the attacker can run arbitrary code in the victim's browser context.
  • Reading application data: attackers may be able to obtain sensitive information.

Typical cross-site scripting goals follow: stealing session information, acting as the user and reading pages the victim can see.

MITRE also stresses that data placed in both the response header and the body needs control, to ensure that no scripting syntax is present.

How to find it

The record lists no detection methods for this weakness. A practical approach:

  1. Inventory every place the application sets a response header from request data. Common ones are redirect targets, cookies, custom debugging headers and content disposition names.
  2. Inventory error handlers and default error pages that echo parts of the request, including headers.
  3. Send requests with header values that include angle brackets, quotes, carriage returns and line feeds, and check whether they come back in the response headers or the error body unchanged.
  4. Include carriage return and line feed sequences, since they point toward response splitting.

Dynamic scanning of a running application finds reflections quickly, and static analysis helps with code that writes headers from request variables. Keep in mind that the Flash-based path in the MITRE example is a legacy one, but the underlying habit of reflecting request data without encoding is not.

How to fix it

MITRE's mitigations:

  1. Perform output validation, meaning filter, escape or encode unsafe data that is passed from the server in an HTTP response header.
  2. Disable script execution functionality in the clients' browser.

The second is outside the developer's direct control, so concentrate on the first.

import re

def safe_header_value(value, limit=200):
    # Remove control characters including CR and LF, then cap length
    cleaned = re.sub(r"[\x00-\x1f\x7f]", "", value)
    return cleaned[:limit]

resp.headers["X-Origin"] = safe_header_value(request.args.get("from", ""))

More habits that help:

  • Do not reflect request headers into error messages. If you must, encode the output for its context.
  • Prefer allowlists. If a header value should be one of three known strings, map to those and ignore anything else.
  • Use framework header APIs that reject line breaks, and keep the framework current.
  • Send a restrictive Content-Type and the right character set so that content is not guessed.

Quick check

Why are headers a place for scripting problems?

Some browser components process raw headers, so script syntax inside a reflected header can execute in the client.

What did CVE-2006-3918 involve?

A web server reflected the Expect header from a request into an error message, allowing a Flash SWF file to perform XSS.

What is the main fix?

Perform output validation: filter, escape or encode unsafe data placed in response headers, as well as in the body.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.