{"title":"HTTP Header Injection (CRLF Injection)","language":"Python","severity":"High","cwe":"CWE-113","source_lines":[5],"flow_lines":[5,8],"sink_lines":[8],"vulnerable_code":"from flask import Flask, request, Response\napp = Flask(__name__)\n@app.route('/iot/redirect')\ndef device_redirect():\n    fw_channel = request.args.get('channel', 'stable')\n    build_tag = request.args.get('build', 'latest')\n    resp = Response(status=302)\n    resp.headers['X-Firmware-Channel'] = fw_channel\n    resp.headers['Location'] = f'https://ota.iot-platform.local/firmware/{build_tag}'\n    resp.headers['X-Build-Tag'] = build_tag\n    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":"from flask import Flask, request, Response, abort\nimport re\n\napp = Flask(__name__)\n\ndef sanitize_header_value(value):\n    \"\"\"Remove CRLF characters and other control characters to prevent header injection.\"\"\"\n    if re.search(r'[\\r\\n\\x00]', value):\n        abort(400, description=\"Invalid characters in parameter\")\n    return value\n\ndef validate_channel(channel):\n    \"\"\"Validate firmware channel against an allowlist.\"\"\"\n    allowed_channels = {'stable', 'beta', 'nightly', 'dev'}\n    if channel not in allowed_channels:\n        abort(400, description=\"Invalid firmware channel\")\n    return channel\n\ndef validate_build_tag(build_tag):\n    \"\"\"Validate build tag format (alphanumeric, dots, hyphens only).\"\"\"\n    if not re.fullmatch(r'[a-zA-Z0-9._-]+', build_tag):\n        abort(400, description=\"Invalid build tag format\")\n    return build_tag\n\n@app.route('/iot/redirect')\ndef device_redirect():\n    fw_channel = request.args.get('channel', 'stable')\n    build_tag = request.args.get('build', 'latest')\n    # Sanitize against CRLF injection\n    fw_channel = sanitize_header_value(fw_channel)\n    build_tag = sanitize_header_value(build_tag)\n    # Validate against expected formats\n    fw_channel = validate_channel(fw_channel)\n    build_tag = validate_build_tag(build_tag)\n    resp = Response(status=302)\n    resp.headers['X-Firmware-Channel'] = fw_channel\n    resp.headers['Location'] = f'https://ota.iot-platform.local/firmware/{build_tag}'\n    resp.headers['X-Build-Tag'] = build_tag\n    return resp"}