{"title":"Open Redirect via Unvalidated Redirect Target","language":"Python","severity":"Medium","cwe":"CWE-601","source_lines":[10],"flow_lines":[10,13],"sink_lines":[13],"vulnerable_code":"from flask import Flask, request, redirect\nimport boto3\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\n@app.route('/cloud/resource-access')\ndef grant_temporary_access():\n    resource_id = request.args.get('resource_id')\n    bucket_name = request.args.get('bucket', 'default-storage')\n    return_path = request.args.get('return_path', '/dashboard')\n    presigned_url = s3_client.generate_presigned_url('get_object', Params={'Bucket': bucket_name, 'Key': resource_id}, ExpiresIn=3600)\n    if presigned_url:\n        return redirect(return_path)\n    return 'Resource not found', 404","explanation":"The application accepts a user-controlled 'return_path' parameter without any validation and passes it directly to the redirect() function. An attacker can manipulate this parameter to redirect users to external malicious sites, enabling phishing attacks or credential theft after legitimate authentication.","remediation":"The fix adds a validation function `is_safe_redirect_path` that ensures the return_path is a relative path by rejecting any URL containing a scheme, netloc, or protocol-relative prefix (//). If the return_path fails validation, it defaults to the safe '/dashboard' path, preventing attackers from redirecting users 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_RETURN_PATHS = ['/dashboard', '/files', '/settings', '/profile']\n\ndef is_safe_redirect_path(target):\n    \"\"\"Validate that the redirect target is a safe relative path.\"\"\"\n    if not target:\n        return False\n    parsed = urlparse(target)\n    # Reject any URL with a scheme or netloc (external redirect)\n    if parsed.scheme or parsed.netloc:\n        return False\n    # Ensure path starts with / (relative to current host)\n    if not target.startswith('/'):\n        return False\n    # Reject protocol-relative URLs (e.g., //evil.com)\n    if target.startswith('//'):\n        return False\n    return True\n\n@app.route('/cloud/resource-access')\ndef grant_temporary_access():\n    resource_id = request.args.get('resource_id')\n    bucket_name = request.args.get('bucket', 'default-storage')\n    return_path = request.args.get('return_path', '/dashboard')\n\n    if not is_safe_redirect_path(return_path):\n        return_path = '/dashboard'\n\n    presigned_url = s3_client.generate_presigned_url('get_object', Params={'Bucket': bucket_name, 'Key': resource_id}, ExpiresIn=3600)\n    if presigned_url:\n        return redirect(return_path)\n    return 'Resource not found', 404"}