{"title":"HTTP Response Splitting via Unvalidated Redirect Location Header","language":"Python","severity":"High","cwe":"CWE-113","source_lines":[8,9],"flow_lines":[8,12,9,13],"sink_lines":[12,13],"vulnerable_code":"from flask import Flask, request, Response\nimport logging\n\napp = Flask(__name__)\n\n@app.route('/iot/device/provision')\ndef provision_device():\n    device_id = request.args.get('device_id', '')\n    callback_endpoint = request.args.get('callback', '/dashboard')\n    logging.info(f'Provisioning device: {device_id}')\n    response = Response('Device provisioned successfully', status=302)\n    response.headers['Location'] = callback_endpoint\n    response.headers['X-Device-ID'] = device_id\n    return response","explanation":"The application accepts untrusted input from request parameters (device_id and callback) and directly injects them into HTTP response headers without validation or sanitization. An attacker can inject CRLF characters (\\r\\n) to split the HTTP response, inject arbitrary headers, or perform HTTP Response Splitting attacks leading to XSS, cache poisoning, or session hijacking.","remediation":"The fix sanitizes both user-controlled inputs before placing them in HTTP headers. CRLF and control characters are stripped from all header values, the redirect URL is validated against an allowlist of safe relative paths to prevent both response splitting and open redirects, and the device_id is validated to only contain alphanumeric characters, hyphens, and underscores.","secure_code":"from flask import Flask, request, Response, abort\nimport logging\nimport re\nfrom urllib.parse import urlparse\n\napp = Flask(__name__)\n\nALLOWED_REDIRECT_PATHS = ['/dashboard', '/devices', '/config', '/status']\n\ndef sanitize_header_value(value):\n    \"\"\"Remove any CRLF characters and other control characters from header values.\"\"\"\n    if value is None:\n        return ''\n    sanitized = re.sub(r'[\\r\\n\\x00-\\x1f\\x7f]', '', value)\n    return sanitized\n\ndef validate_redirect_url(url):\n    \"\"\"Validate that the redirect URL is a safe relative path.\"\"\"\n    sanitized = sanitize_header_value(url)\n    parsed = urlparse(sanitized)\n    if parsed.scheme or parsed.netloc:\n        return '/dashboard'\n    if not sanitized.startswith('/'):\n        return '/dashboard'\n    path_base = sanitized.split('?')[0]\n    if path_base not in ALLOWED_REDIRECT_PATHS:\n        return '/dashboard'\n    return sanitized\n\ndef validate_device_id(device_id):\n    \"\"\"Validate device_id contains only safe characters.\"\"\"\n    if not device_id:\n        return ''\n    if not re.match(r'^[a-zA-Z0-9_\\-]+$', device_id):\n        abort(400, description='Invalid device_id format')\n    return device_id\n\n@app.route('/iot/device/provision')\ndef provision_device():\n    device_id = request.args.get('device_id', '')\n    callback_endpoint = request.args.get('callback', '/dashboard')\n    device_id = validate_device_id(device_id)\n    safe_callback = validate_redirect_url(callback_endpoint)\n    logging.info(f'Provisioning device: {device_id}')\n    response = Response('Device provisioned successfully', status=302)\n    response.headers['Location'] = safe_callback\n    if device_id:\n        response.headers['X-Device-ID'] = device_id\n    return response"}