{"title":"Open Redirect via Unvalidated Flask Redirect Target","language":"Python","severity":"Medium","cwe":"CWE-601","source_lines":[11],"flow_lines":[11,14],"sink_lines":[14],"vulnerable_code":"from flask import Flask, request, redirect\nimport boto3\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\n@app.route('/cloud/asset/download')\ndef fetch_cloud_asset():\n    asset_id = request.args.get('asset_id')\n    bucket_name = request.args.get('bucket', 'default-assets')\n    redirect_url = request.args.get('return_to', '/dashboard')\n    if asset_id:\n        s3_client.head_object(Bucket=bucket_name, Key=asset_id)\n    return redirect(redirect_url)","explanation":"The application accepts a user-controlled 'return_to' parameter and passes it directly to Flask's redirect() function without validation. An attacker can manipulate this parameter to redirect users to arbitrary external malicious sites, enabling phishing attacks or credential theft.","remediation":"The fix introduces a URL validation function that ensures the redirect target is a relative path (no scheme or netloc) and matches a whitelist of allowed path prefixes. If the provided 'return_to' parameter fails validation, the application defaults to redirecting to '/dashboard', preventing open redirect attacks to external malicious sites.","secure_code":"from flask import Flask, request, redirect, abort\nfrom urllib.parse import urlparse\nimport boto3\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\nALLOWED_PATHS = ['/dashboard', '/assets', '/files', '/home']\n\ndef is_safe_redirect_url(target):\n    \"\"\"Validate that the redirect target is a safe, relative URL.\"\"\"\n    if not target:\n        return False\n    parsed = urlparse(target)\n    # Reject any URL with a scheme or netloc (external URLs)\n    if parsed.scheme or parsed.netloc:\n        return False\n    # Ensure the path starts with / and doesn't use protocol-relative URLs\n    if not target.startswith('/') or target.startswith('//'):\n        return False\n    # Optionally whitelist specific path prefixes\n    if not any(target.startswith(allowed) for allowed in ALLOWED_PATHS):\n        return False\n    return True\n\n@app.route('/cloud/asset/download')\ndef fetch_cloud_asset():\n    asset_id = request.args.get('asset_id')\n    bucket_name = request.args.get('bucket', 'default-assets')\n    redirect_url = request.args.get('return_to', '/dashboard')\n    if asset_id:\n        s3_client.head_object(Bucket=bucket_name, Key=asset_id)\n    if not is_safe_redirect_url(redirect_url):\n        redirect_url = '/dashboard'\n    return redirect(redirect_url)"}