Crlf Injection in Flask with Api Keys
Crlf Injection in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when user-controlled data is reflected in HTTP headers without sanitization, allowing an attacker to inject newline characters (%0D%0A or \r\n) to split headers and inject additional ones. In Flask, this commonly happens when API keys or other dynamic values are placed into custom response headers. Consider a route that echoes an API key into a header such as x-api-key or uses a key to drive a redirect or a custom header value:
from flask import Flask, request, make_response
app = Flask(__name__)
@app.route("/lookup")
def lookup():
api_key = request.args.get("api_key", "")
# Dangerous: directly using user input in a header
resp = make_response({"message": "ok"})
resp.headers["X-API-Key-Echo"] = api_key
return resp
If an attacker provides api_key=abc%0D%0ASet-Cookie:%20session=evil, the header line break splits the response, causing the injected Set-Cookie header to be interpreted by the client. This can lead to session fixation or other header-based attacks. The same risk applies when API keys are used to select server-side behavior (e.g., routing, versioning, or tenant identification) and reflected in headers, trailers, or cookies.
The vulnerability is compounded when:
- API keys are logged or echoed in headers for debugging, inadvertently exposing them in transit if injection manipulates the header structure.
- Flask extensions or custom wrappers process keys and forward them into headers without canonicalization, allowing multi-line input to bypass expected header formats.
- The application relies on header-based authorization checks that can be bypassed by injecting additional headers such as
Authorization:or manipulatingSet-Cookie.
Because Crlf Injection is a web-layer issue, it maps to the OWASP API Top 10 category API1:2023 – Broken Object Level Authorization when injected headers influence authorization decisions, and it can intersect with data exposure if injected headers cause sensitive information to be revealed. The risk is not theoretical; similar patterns have been seen in the wild where header injection combined with API key handling led to privilege escalation or information leakage.
Detection methods check whether API key inputs appear in response headers, cookies, or location values without proper filtering. The scanner runs a sequence of probes that include newline-encoded characters in key-like parameters and verifies whether the server’s response contains a second, attacker-controlled header line, indicating successful injection.
Api Keys-Specific Remediation in Flask — concrete code fixes
Remediation centers on strict input validation and safe header construction. Never reflect raw API keys or any user input into HTTP headers. If you must associate a request with a key for logging or tracing, store the mapping server-side (e.g., in a short-lived cache) and use an opaque identifier in headers.
Safe approach — validate and sanitize before using in headers:
import re
from flask import Flask, request, make_response
app = Flask(__name__)
# Allow only safe characters for an API key header value (alphanumeric and a few symbols)
def is_safe_key_value(value: str) -> bool:
return re.fullmatch(r"[A-Za-z0-9\-._~+/=]+", value) is not None
@app.route("/lookup")
def lookup():
api_key = request.args.get("api_key", "")
if not is_safe_key_value(api_key):
return {"error": "invalid api_key"}, 400
# Use a server-side mapping for audit/trace identifiers instead of echoing the key
resp = make_response({"message": "ok"})
resp.headers["X-Request-Id"] = "req-12345" # opaque identifier
return resp
If you need to return the key in a header for client correlation, hash or truncate it rather than echoing raw input:
import hashlib
from flask import Flask, request, make_response
app = Flask(__name__)
@app.route("/lookup")
def lookup():
api_key = request.args.get("api_key", "")
# Validate and avoid newline or control characters
if "\n" in api_key or "\r" in api_key:
return {"error": "invalid api_key"}, 400
# Store server-side, send opaque reference
key_hash = hashlib.sha256(api_key.encode("utf-8")).hexdigest()[:16]
resp = make_response({"message": "ok"})
resp.headers["X-Api-Key-Hint"] = key_hash
return resp
Additionally, apply these Flask-level defenses:
- Use
werkzeug.datastructures.Headersand preferresp.headersover manually concatenated strings to avoid accidental injection. - Set
Response.headersexplicitly rather than allowing frameworks or middleware to append user input into headers. - Employ strict Content Security Policy and avoid exposing API keys in URLs where they might be logged in Referer headers.
For production, combine these coding practices with automated scanning. The middleBrick CLI can be integrated into development workflows to detect header injection and related issues early:
middlebrick scan https://api.example.com/lookup
Teams using the Pro plan can enable continuous monitoring so that any regression in header handling is flagged before deployment, and the GitHub Action can fail builds when risk thresholds are exceeded.