HIGH crlf injectionflaskhmac signatures

Crlf Injection in Flask with Hmac Signatures

Crlf Injection in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Crlf Injection occurs when an attacker can inject carriage return (CR, \r) and line feed (\n) sequences into an HTTP header or header-like value. In Flask, if user-controlled data such as a username, redirect target, or a custom header value is reflected in a response header and the developer uses Hmac Signatures only for request authentication (e.g., signing query parameters or a JSON body) without also validating or sanitizing header values, the signature may still be considered valid while the injected CR/LF changes the header structure. This can lead to header splitting, where additional headers are injected, or HTTP response splitting, which may enable cross-site scripting (XSS) via injected headers like Location or Set-Cookie.

Consider a Flask route that uses Hmac Signatures to verify a webhook or API request. The server verifies the Hmac to ensure the request originates from a trusted source, but then reflects a user-supplied value into a response header without sanitization:

from flask import Flask, request, make_response
import hmac
import hashlib

app = Flask(__name__)
SECRET = b'super-secret-key'

def verify_hmac(data, received_signature):
    computed = hmac.new(SECRET, data, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, received_signature)

@app.route('/webhook')
def webhook():
    data = request.args.get('data', '')
    sig = request.args.get('sig', '')
    if not verify_hmac(data.encode(), sig):
        return 'Invalid signature', 400
    # Vulnerable: user-controlled 'next' used in a header
    next_url = request.args.get('next', '/')
    resp = make_response('OK')
    resp.headers['X-Redirect'] = next_url  # No sanitization
    return resp

An attacker could supply next as example.com\r\nSet-Cookie: session=hijacked. If the header value is reflected without validation, the injected CRLF can create a second header line. Because the Hmac was computed only over the request payload and not over the reflected header values, the server still accepts the request as authenticated, but the response header is split, potentially allowing injected cookies or a crafted Location header for open redirects. This illustrates that Hmac Signatures protect the integrity of the request but do not automatically protect response header construction; explicit output encoding and header value validation are required.

Hmac Signatures-Specific Remediation in Flask — concrete code fixes

To prevent Crlf Injection when using Hmac Signatures in Flask, treat all user-controlled data that may reach response headers as untrusted, even when the request’s Hmac is valid. Apply canonical header value rules: disallow CR and LF characters, and sanitize or encode values before assigning them to headers. Below are concrete remediation patterns and code examples.

1. Reject or sanitize header values

Before assigning a user-controlled value to a header, strip or replace CR and LF characters. For string values that should be opaque (e.g., a redirect path), use strict validation (whitelist) or percent-encoding where appropriate.

import re
from flask import Flask, request, make_response
import hmac
import hashlib

app = Flask(__name__)
SECRET = b'super-secret-key'

def verify_hmac(data, received_signature):
    computed = hmac.new(SECRET, data, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, received_signature)

SANITIZE_CRLF_RE = re.compile(r'[\r\n]')

def safe_header_value(value):
    # Remove CR/LF to prevent header splitting
    return SANITIZE_CRLF_RE.sub('', value)

@app.route('/webhook')
def webhook():
    data = request.args.get('data', '')
    sig = request.args.get('sig', '')
    if not verify_hmac(data.encode(), sig):
        return 'Invalid signature', 400
    next_url = request.args.get('next', '/')
    safe_next = safe_header_value(next_url)
    resp = make_response('OK')
    resp.headers['X-Redirect'] = safe_next
    return resp

2. Use strict allowlists for known-safe values

For paths used in redirects or locations, prefer a strict allowlist approach. Parse the target, ensure it is a relative path or matches an allowed host, and avoid reflecting raw user input into headers.

from urllib.parse import urlparse

ALLOWED_HOSTS = {'app.example.com', 'cdn.example.com'}

def is_safe_url(url):
    if not url:
        return False
    # Relative URLs are generally safe for certain headers; ensure no CRLF
    if url.startswith('/'):
        return '\r' not in url and '\n' not in url
    parsed = urlparse(url)
    if parsed.hostname not in ALLOWED_HOSTS:
        return False
    return '\r' not in url and '\n' not in url

@app.route('/redirect')
def redirect_user():
    target = request.args.get('to', '/')
    if not is_safe_url(target):
        return 'Invalid destination', 400
    resp = make_response('Redirecting')
    resp.headers['X-Safe-Location'] = target
    return resp

3. Encode when reflection is unavoidable

If you must reflect a value into a header context, apply encoding that neutralizes control characters. For non-text header values, base64 encoding can be a safe choice, provided the consumer expects it.

import base64

@app.route('/data')
def data_endpoint():
    token = request.args.get('token', '')
    # Encode token to avoid injection while preserving content
    encoded_token = base64.urlsafe_b64encode(token.encode()).decode()
    resp = make_response('Data')
    resp.headers['X-Token'] = encoded_token
    return resp

These patterns complement Hmac Signatures by ensuring that even authenticated requests cannot lead to malformed headers. They address the root cause—untrusted data in headers—rather than relying solely on request integrity checks.

Frequently Asked Questions

Do Hmac Signatures alone prevent Crlf Injection in Flask?
No. Hmac Signatures verify the integrity of the request payload or parameters, but they do not sanitize or validate values that are later reflected into HTTP headers. You must explicitly reject or encode CR/LF characters in any header-bound user input.
Should I also encode or allowlist values used in Location headers for open redirect protection?
Yes. Treat Location header values as untrusted. Use strict allowlists for redirect targets or encode/remove CR/LF characters. Do not rely on Hmac validation alone to prevent open redirects or header splitting.