# HTTP Header Injection (CRLF Injection)

Language: Python
Severity: High
CWE: CWE-113

## Source
5

## Flow
5-8

## Sink
8

## Vulnerable Code
```python
from flask import Flask, request, Response
app = Flask(__name__)
@app.route('/iot/redirect')
def device_redirect():
    fw_channel = request.args.get('channel', 'stable')
    build_tag = request.args.get('build', 'latest')
    resp = Response(status=302)
    resp.headers['X-Firmware-Channel'] = fw_channel
    resp.headers['Location'] = f'https://ota.iot-platform.local/firmware/{build_tag}'
    resp.headers['X-Build-Tag'] = build_tag
    return resp
```

## Explanation

The 'fw_channel' and 'build_tag' variables are directly taken from user-controlled query parameters without any sanitization of CRLF sequences (\r\n). When these values are assigned to HTTP response headers, an attacker can inject arbitrary newline characters to terminate the current header and inject additional headers or split the HTTP response body, leading to HTTP Response Splitting / CRLF Injection.

## Remediation

The fix introduces a multi-layered defense: first, a sanitize_header_value function rejects any input containing CR (\r), LF (\n), or null (\x00) characters by returning a 400 error, which directly prevents CRLF injection. Second, domain-specific validation functions enforce that channel must belong to a strict allowlist of known firmware channels and build_tag must match an alphanumeric pattern with dots and hyphens only, eliminating any possibility of injecting special characters into response headers. Both user-controlled inputs are validated before being used in any header assignment.

## Secure Code
```python
from flask import Flask, request, Response, abort
import re

app = Flask(__name__)

def sanitize_header_value(value):
    """Remove CRLF characters and other control characters to prevent header injection."""
    if re.search(r'[\r\n\x00]', value):
        abort(400, description="Invalid characters in parameter")
    return value

def validate_channel(channel):
    """Validate firmware channel against an allowlist."""
    allowed_channels = {'stable', 'beta', 'nightly', 'dev'}
    if channel not in allowed_channels:
        abort(400, description="Invalid firmware channel")
    return channel

def validate_build_tag(build_tag):
    """Validate build tag format (alphanumeric, dots, hyphens only)."""
    if not re.fullmatch(r'[a-zA-Z0-9._-]+', build_tag):
        abort(400, description="Invalid build tag format")
    return build_tag

@app.route('/iot/redirect')
def device_redirect():
    fw_channel = request.args.get('channel', 'stable')
    build_tag = request.args.get('build', 'latest')
    # Sanitize against CRLF injection
    fw_channel = sanitize_header_value(fw_channel)
    build_tag = sanitize_header_value(build_tag)
    # Validate against expected formats
    fw_channel = validate_channel(fw_channel)
    build_tag = validate_build_tag(build_tag)
    resp = Response(status=302)
    resp.headers['X-Firmware-Channel'] = fw_channel
    resp.headers['Location'] = f'https://ota.iot-platform.local/firmware/{build_tag}'
    resp.headers['X-Build-Tag'] = build_tag
    return resp
```
