HIGH crlf injectionflaskpython

Crlf Injection in Flask (Python)

Crlf Injection in Flask with Python

Crlf Injection occurs when an attacker can inject a carriage return (CR, \r) and line feed (LF, \n) sequence into a header or status line, causing the header to be split and additional headers or response content to be injected. In Flask, this typically arises when user-controlled data is placed into HTTP headers, response status codes, or redirect locations without proper sanitization. Because Flask builds responses using Python strings that are ultimately serialized into raw HTTP bytes, unsanitized inputs can produce sequences like \r\n that alter the structure of the response.

For example, if a Flask route uses request query parameters to set a redirect location or a custom header, and does not validate or encode newlines, an attacker can inject extra headers such as Content-Security-Policy or set status codes to misleading values. Consider this vulnerable pattern:

from flask import Flask, request, make_response

app = Flask(__name__)

@app.route("/redirect")
def redirect_user():
    url = request.args.get("url", "/")
    # Vulnerable: user input placed directly into redirect location
    return f"redirect {url}", 302

If the provided url contains \r\n, the injected sequence can create a second header or body content, which may lead to header manipulation, cache poisoning, or open redirects depending on how the response is handled by clients and intermediaries. Flask does not inherently sanitize these values because it is a lightweight framework that expects developers to validate and encode external data. The risk is amplified when the framework is used with Python string concatenation or formatting, as newlines can be trivially injected without encoding.

Another common scenario is header injection via custom headers:

@app.route("/set-header")
def set_header():
    name = request.args.get("name", "X-Value")
    val = request.args.get("val", "test")
    # Vulnerable: unsanitized header name/value
    response = make_response("ok")
    response.headers[name] = val
    return response

An attacker can supply name with "X-Injected\r\nSet-Cookie: session=attacker" and potentially introduce unintended headers. Because HTTP response splitting relies on precise CR and LF placement, Python’s string handling makes it straightforward for an attacker to craft payloads that bypass naive checks that only look for one character.

Python-Specific Remediation in Flask

Remediation focuses on disallowing CR and LF characters in any user-controlled data that flows into headers, status codes, or redirect targets. In Python, you can explicitly reject or strip these characters before using the data. Below are concrete, safe patterns for the two scenarios described above.

For redirects, validate the URL and ensure it does not contain control characters. Use Python’s string methods or a small validation helper:

from flask import Flask, request, redirect, abort
import re

app = Flask(__name__)

# Allow only safe characters for URLs in redirects
SAFE_URL_RE = re.compile(r"^[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$")

def is_safe_url(url: str) -> bool:
    # Reject strings containing CR or LF
    if "\r" in url or "\n" in url:
        return False
    # Optionally enforce a basic URL pattern
    return bool(SAFE_URL_RE.match(url))

@app.route("/redirect")
def redirect_user():
    url = request.args.get("url", "/")
    if not is_safe_url(url):
        abort(400, "Invalid redirect URL")
    return redirect(url, code=302)

For custom headers, sanitize both the name and value by rejecting or stripping CR and LF. Using make_response and direct header assignment, you can enforce this at assignment time:

from flask import Flask, request, make_response

app = Flask(__name__)

@app.route("/set-header")
def set_header():
    raw_name = request.args.get("name", "X-Value")
    raw_val = request.args.get("val", "test")
    # Remove CR and LF characters to prevent injection
    name = raw_name.replace("\r", "").replace("\n", "")
    val = raw_val.replace("\r", "").replace("\n", "")
    response = make_response("ok")
    # Reject empty header names
    if not name:
        return "Invalid header name", 400
    response.headers[name] = val
    return response

Additionally, prefer using Flask’s built-in abstractions like redirect() and make_response() rather than manually constructing status lines or header strings. For broader protection across an application, consider a small utility module that all user inputs pass through before being used in headers or status lines. This is especially important when integrating with the middleBrick CLI for automated scans; running middlebrick scan <url> can identify these patterns in your unauthenticated attack surface and highlight where newlines remain reachable.

Finally, map findings to compliance frameworks such as OWASP API Top 10 (2023) which includes Security Misconfiguration and Injection risks. Regular scans using tools like the middleBrick GitHub Action can fail builds if new risky endpoints are introduced, helping maintain secure deployment practices without requiring manual header audits for every change.

Frequently Asked Questions

Can a simple newline in a header name break parsing?
Yes. Because HTTP header lines are separated by \r\n, injecting these characters can cause an extra header to be parsed or the message body to be misinterpreted as a header.
Does middleBrick test for Crlf Injection in Flask apps?
Yes. middleBrick runs 12 security checks including Input Validation and Header handling, and it reports findings with severity and remediation guidance for unauthenticated attack surfaces.